寻找递增子序列
力扣原题链接
问题描述
给定一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中至少有两个元素。你可以按任意顺序返回答案。数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例
示例 1:
输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]
示例 2:
输入:nums = [4,4,3,2,1]
输出:[[4,4]]
解题思路
- 初始化一个列表
res
用于存储结果。 - 调用回溯函数
backtrack
,传入参数nums
数组、起始索引0
、当前路径path
和结果列表res
。 - 在回溯函数中,首先将当前路径
path
加入到结果列表res
中。 - 然后从起始索引
start
开始向后遍历数组nums
,依次选择元素加入当前路径path
中。 - 在选择元素加入路径之前,需要判断是否满足递增条件和是否重复,如果满足条件则递归调用回溯函数,继续探索下一个位置的选择。
- 当遍历结束后,回溯函数结束,返回结果列表
res
。
复杂度分析
回溯算法的时间复杂度取决于最终的结果数量,而结果数量取决于数组 nums
中满足条件的递增子序列的个数。假设数组 nums
的长度为 n
,则时间复杂度为 O(2^n)
,其中 n
表示数组的长度。
算法步骤
- 初始化一个列表
res
用于存储结果。 - 调用回溯函数
backtrack
,传入参数nums
数组、起始索引0
、当前路径path
和结果列表res
。 - 在回溯函数中,首先将当前路径
path
加入到结果列表res
中。 - 遍历数组
nums
,从索引start
开始,依次选择元素加入当前路径path
中。 - 在选择元素加入路径之前,需要进行判断:
- 如果当前路径的长度大于等于
2
,则将当前路径加入结果列表。 - 使用一个哈希集合
used
来记录已经选择过的元素,以避免重复选择。 - 如果选择的元素小于当前路径的最后一个元素,则跳过,保证递增。
- 如果当前元素已经被选择过,则跳过,避免重复。
- 如果当前路径的长度大于等于
- 递归调用回溯函数,传入更新后的起始索引
i + 1
、当前路径path
和结果列表res
。 - 当遍历结束后,回溯函数结束,返回结果列表
res
。
java代码
class Solution {List<List<Integer>> res = new ArrayList<>();// 主函数,用于找到数组中的递增子序列public List<List<Integer>> findSubsequences(int[] nums) {backtrack(nums, 0, new ArrayList<>()); // 回溯函数的入口return res; // 返回结果列表}// 回溯函数,用于寻找数组中的递增子序列private void backtrack(int[] nums, int start, List<Integer> path) {// 当路径中元素数量大于等于 2 时,将当前路径加入结果列表中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)) {continue;}// 树枝剪枝操作// 若当前数字已经使用过,则跳过当前数字,避免重复if (used.contains(nums[i])) {continue;}// 将当前数字加入路径中,并标记为已使用used.add(nums[i]);path.add(nums[i]);// 递归进入下一层,继续寻找递增子序列backtrack(nums, i + 1, path);// 回溯,撤销选择,将当前数字移出路径path.remove(path.size() - 1);}}
}
通过使用回溯算法,我们可以有效地找到数组中所有满足条件的递增子序列。在回溯过程中,我们需要维护一个当前路径,逐步探索所有可能的选择,并及时剪枝以避免重复。