{x}
blog image

Non-decreasing Subsequences

Given an integer array nums, return all the different possible non-decreasing subsequences of the given array with at least two elements. You may return the answer in any order.

 

Example 1:

Input: nums = [4,6,7,7]
Output: [[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]

Example 2:

Input: nums = [4,4,3,2,1]
Output: [[4,4]]

 

Constraints:

  • 1 <= nums.length <= 15
  • -100 <= nums[i] <= 100

Solution Explanation for Non-Decreasing Subsequences

This problem asks to find all non-decreasing subsequences of length at least 2 from a given integer array. The solution utilizes a Depth-First Search (DFS) approach to efficiently explore all possible subsequences.

Approach

The core idea is to recursively explore the input array (nums). The dfs function takes the following parameters:

  • u: The current index being considered in the nums array.
  • last: The last element added to the current subsequence (t). This ensures non-decreasing order.
  • t: The current subsequence being built.

The dfs function operates as follows:

  1. Base Case: If u reaches the end of the nums array, it checks if the length of the current subsequence t is greater than 1. If it is, the subsequence is added to the result (ans).

  2. Recursive Step: If the current element nums[u] is greater than or equal to the last element in the subsequence, it means we can extend the subsequence. We add nums[u] to t, recursively call dfs with the updated last and t, and then backtrack by removing nums[u] from t.

  3. Skipping Element: If nums[u] is different from the last element, it means we can choose to skip the current element and continue exploring subsequences from the next element without adding the current element to the subsequence. This handles cases where we might have duplicate elements.

This recursive exploration systematically generates all possible non-decreasing subsequences of length at least 2.

Code Explanation (Python3 as an Example)

class Solution:
    def findSubsequences(self, nums: List[int]) -> List[List[int]]:
        def dfs(u, last, t):
            if u == len(nums):
                if len(t) > 1:
                    ans.append(t[:]) #Append a copy to avoid modification
                return
            if nums[u] >= last:
                t.append(nums[u])
                dfs(u + 1, nums[u], t)
                t.pop() # Backtrack
            if nums[u] != last:
                dfs(u + 1, last, t)
 
        ans = []
        dfs(0, -1000, []) #Initialize with a very small last value
        return ans

The Python code mirrors the approach directly. The -1000 initialization for last ensures that the first element is always considered. The t[:] slice creates a copy to avoid modifying the original list during appending to ans.

Time and Space Complexity Analysis

  • Time Complexity: O(N * 2N). In the worst case (all elements are unique and non-decreasing), the number of subsequences can grow exponentially with N (the length of the input array). The DFS explores all these possibilities.

  • Space Complexity: O(N * 2N) in the worst case. The space is primarily used to store the resulting subsequences. The recursion depth can also be at most N. Therefore, the space complexity is dominated by the size of the ans list.

The exponential time and space complexity is inherent in the problem because the number of possible subsequences can be very large. For small input arrays (N ≤ 15 as specified), the solution remains practical. For much larger arrays, more sophisticated techniques might be needed to improve performance or handle memory constraints.