Given an array of integers nums
, find all the different non-decreasing subsequences of the given array, where the length of each subsequence is at least 2.
a
and b
in the subsequence, a <= b
.
Example:
Input: nums = [4, 6, 7, 7]
Output: [[4,6], [4,7], [4,6,7], [4,6,7,7], [6,7], [6,7,7], [7,7], [4,7,7]]
At first glance, the problem resembles generating all possible subsequences of an array. However, there are two major constraints: subsequences must be non-decreasing, and duplicates must be avoided.
The challenge is to efficiently explore all valid paths and avoid redundant work.
We use a backtracking algorithm to systematically build all non-decreasing subsequences of length at least 2, while avoiding duplicates.
This approach ensures that every non-decreasing subsequence is explored, but duplicates are avoided by careful bookkeeping.
Let's walk through the input nums = [4, 6, 7, 7]
:
nums[i]
if it's non-decreasing.
4
), add to the path: [4]
.
6
), since 6 >= 4
, path becomes [4, 6]
. This is a valid subsequence (length >= 2), so add to result.
7
), 7 >= 6
, path is [4, 6, 7]
, add to result.
7
), 7 >= 7
, path is [4, 6, 7, 7]
, add to result.
[4, 6]
, try index 3 (7
), path is [4, 6, 7]
(already added, but set will prevent duplicates).
[4]
, try index 2 (7
), path is [4, 7]
, add to result, continue to index 3 (7
), path is [4, 7, 7]
, add to result.
6
) and so on, always ensuring non-decreasing order and avoiding duplicates.
The process systematically builds all valid subsequences and the set ensures uniqueness.
The key to solving the Non-decreasing Subsequences problem is efficiently generating all valid subsequences using backtracking while avoiding duplicates with a set. By only extending non-decreasing paths and keeping track of already-used numbers at each recursion level, we avoid unnecessary work and ensure that the output contains all unique, valid subsequences of length at least 2.
class Solution:
def findSubsequences(self, nums):
res = set()
def backtrack(start, path):
if len(path) >= 2:
res.add(tuple(path))
used = set()
for i in range(start, len(nums)):
if (not path or nums[i] >= path[-1]) and nums[i] not in used:
used.add(nums[i])
backtrack(i + 1, path + [nums[i]])
backtrack(0, [])
return [list(seq) for seq in res]
class Solution {
public:
vector<vector<int>> findSubsequences(vector<int>& nums) {
set<vector<int>> res;
vector<int> path;
backtrack(nums, 0, path, res);
return vector<vector<int>>(res.begin(), res.end());
}
private:
void backtrack(vector<int>& nums, int start, vector<int>& path, set<vector<int>>& res) {
if (path.size() >= 2) {
res.insert(path);
}
unordered_set<int> used;
for (int i = start; i < nums.size(); ++i) {
if ((path.empty() || nums[i] >= path.back()) && !used.count(nums[i])) {
used.insert(nums[i]);
path.push_back(nums[i]);
backtrack(nums, i + 1, path, res);
path.pop_back();
}
}
}
};
class Solution {
public List<List<Integer>> findSubsequences(int[] nums) {
Set<List<Integer>> res = new HashSet<>();
backtrack(nums, 0, new ArrayList<>(), res);
return new ArrayList<>(res);
}
private void backtrack(int[] nums, int start, List<Integer> path, Set<List<Integer>> res) {
if (path.size() >= 2) {
res.add(new ArrayList<>(path));
}
Set<Integer> used = new HashSet<>();
for (int i = start; i < nums.length; i++) {
if ((path.isEmpty() || nums[i] >= path.get(path.size() - 1)) && !used.contains(nums[i])) {
used.add(nums[i]);
path.add(nums[i]);
backtrack(nums, i + 1, path, res);
path.remove(path.size() - 1);
}
}
}
}
var findSubsequences = function(nums) {
const res = new Set();
function backtrack(start, path) {
if (path.length >= 2) {
res.add(path.join(','));
}
const used = new Set();
for (let i = start; i < nums.length; i++) {
if ((path.length === 0 || nums[i] >= path[path.length - 1]) && !used.has(nums[i])) {
used.add(nums[i]);
backtrack(i + 1, path.concat(nums[i]));
}
}
}
backtrack(0, []);
return Array.from(res).map(s => s.split(',').map(Number));
};