A clear explanation of generating all distinct non-decreasing subsequences using DFS, backtracking, and per-level duplicate control.
Problem Restatement
We are given an integer array nums.
We need to return all different non-decreasing subsequences with length at least 2.
A subsequence keeps the original order, but may skip elements.
A sequence is non-decreasing if every next value is greater than or equal to the previous value.
The answer can be returned in any order. The constraints are 1 <= nums.length <= 15 and -100 <= nums[i] <= 100.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums |
| Output | All distinct non-decreasing subsequences |
| Minimum length | At least 2 |
| Order rule | Keep original index order |
| Duplicate rule | Return each value sequence only once |
Function shape:
def findSubsequences(nums: list[int]) -> list[list[int]]:
...Examples
Example 1:
nums = [4, 6, 7, 7]Valid subsequences include:
[4, 6]
[4, 6, 7]
[4, 6, 7, 7]
[4, 7]
[4, 7, 7]
[6, 7]
[6, 7, 7]
[7, 7]Answer:
[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]Example 2:
nums = [4, 4, 3, 2, 1]The only valid non-decreasing subsequence of length at least two is:
[4, 4]Answer:
[[4, 4]]First Thought: Generate Every Subsequence
Since nums.length <= 15, we can think about generating subsequences.
For every element, we choose either:
| Choice | Meaning |
|---|---|
| Take it | Add it to the current subsequence |
| Skip it | Move past it |
That gives at most:
2^nsubsequences.
After generating one, we can check whether it is non-decreasing and has length at least 2.
This works, but duplicate values cause duplicate subsequences.
For example:
nums = [4, 6, 7, 7]The subsequence [4, 7] can be created by choosing the first 7 or the second 7.
We need a cleaner way to avoid duplicates while building.
Key Insight
Use backtracking.
At each recursion level, we choose the next element to append to the current path.
To keep the path non-decreasing, we may append nums[i] only when:
not path or nums[i] >= path[-1]To avoid duplicates, use a local set at each recursion level.
The local set records which values have already been chosen as the next value from this exact position.
For example, if two 7s are available at the same recursion level, we should only start one branch with 7.
This removes duplicate subsequences without blocking valid repeated values like [7, 7].
Algorithm
Define a DFS function:
dfs(start)where start is the first index we are allowed to consider.
Maintain a list path.
Inside dfs(start):
- If
len(path) >= 2, add a copy ofpathto the answer. - Create an empty set
used. - For each index
ifromstartto the end:- Skip
nums[i]if it was already used at this recursion level. - Skip
nums[i]if it would makepathdecreasing. - Otherwise, append
nums[i]. - Recurse with
dfs(i + 1). - Pop it from
path.
- Skip
Correctness
The DFS builds subsequences in increasing index order because after choosing index i, the next recursive call starts at i + 1. Therefore, every generated sequence is a valid subsequence of nums.
The condition:
not path or nums[i] >= path[-1]ensures every appended value is at least the previous value. Therefore, every generated path is non-decreasing.
Whenever the path length is at least 2, the algorithm adds it to the answer, so every generated valid subsequence is recorded.
Now consider any valid non-decreasing subsequence. Its indices appear in increasing order. At each step, the DFS can choose the next required index because the value satisfies the non-decreasing condition. Therefore, the DFS reaches that subsequence and records it.
The used set at each recursion level prevents two branches from choosing the same value as the next element from the same start position. Those branches would produce duplicate value sequences. Since it only blocks duplicate choices at the same depth, it still allows the same value to appear again later in the path, such as [7, 7].
Thus the algorithm returns exactly all distinct non-decreasing subsequences of length at least 2.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n * 2^n) | There can be exponentially many subsequences, and copying each path costs up to O(n) |
| Space | O(n) excluding output | Recursion depth and current path are at most n |
The output itself may also contain exponentially many subsequences.
Implementation
class Solution:
def findSubsequences(self, nums: list[int]) -> list[list[int]]:
ans = []
path = []
def dfs(start: int) -> None:
if len(path) >= 2:
ans.append(path[:])
used = set()
for i in range(start, len(nums)):
if nums[i] in used:
continue
if path and nums[i] < path[-1]:
continue
used.add(nums[i])
path.append(nums[i])
dfs(i + 1)
path.pop()
dfs(0)
return ansCode Explanation
The result list stores all valid subsequences:
ans = []The current subsequence is stored in:
path = []When the current path has length at least two, it is a valid answer:
if len(path) >= 2:
ans.append(path[:])We copy with path[:] because path will keep changing during backtracking.
The local set prevents duplicate choices at this recursion depth:
used = set()This skips repeated values that would create the same branch:
if nums[i] in used:
continueThis keeps the subsequence non-decreasing:
if path and nums[i] < path[-1]:
continueAfter choosing a value, we recurse only into later indices:
dfs(i + 1)Then we undo the choice:
path.pop()Testing
Since the answer may be returned in any order, normalize before comparing.
def normalize(items):
return sorted(tuple(x) for x in items)
def run_tests():
s = Solution()
assert normalize(s.findSubsequences([4, 6, 7, 7])) == normalize([
[4, 6],
[4, 6, 7],
[4, 6, 7, 7],
[4, 7],
[4, 7, 7],
[6, 7],
[6, 7, 7],
[7, 7],
])
assert normalize(s.findSubsequences([4, 4, 3, 2, 1])) == normalize([
[4, 4],
])
assert normalize(s.findSubsequences([1, 2, 3])) == normalize([
[1, 2],
[1, 2, 3],
[1, 3],
[2, 3],
])
assert normalize(s.findSubsequences([3, 2, 1])) == []
assert normalize(s.findSubsequences([1, 1, 1])) == normalize([
[1, 1],
[1, 1, 1],
])
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[4, 6, 7, 7] | Main example with duplicates |
[4, 4, 3, 2, 1] | Only one duplicate-based answer |
[1, 2, 3] | Fully increasing array |
[3, 2, 1] | No valid length-two non-decreasing subsequence |
[1, 1, 1] | Repeated equal values without duplicate outputs |