Leetcode491. 非递减子序列

从整数数组中找出所有不同递增子序列问题

题目描述

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

示例 1:

javascript 复制代码
输入: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:

javascript 复制代码
输入:nums = [4,4,3,2,1]
输出:[[4,4]]

为何选择回溯法

子序列问题本质上是对所有可能组合的枚举,回溯法(深度优先搜索 DFS 结合剪枝)是处理这类问题的有效手段。由于子序列需保持元素相对顺序,不能随意打乱,所以只能按顺序遍历数组,这与回溯法的特性相契合。

解题关键点

  1. 递增约束 :在构建子序列过程中,新加入的元素必须大于或等于子序列的最后一个元素 ,即使数组中存在重复元素,像 [2,2] 这样的情况也被视为合法递增子序列。

  2. 去重处理 :鉴于数组可能存在重复元素,需要确保生成的子序列各不相同。例如,当数组中有重复的 7 时,不能出现两个相同的 [4,7] 子序列。

  3. 剪枝优化 :在每一层递归中,跳过已经使用过的相同元素,通过这种横向去重的方式,减少不必要的计算(同时也是重),提高算法效率。

回溯框架(横向遍历纵向递归)

  1. 递归参数

    • start:用于标记当前遍历的起始位置,避免重复选择前面已经处理过的元素。

    • path:记录当前已经选择的子序列元素。

  2. 终止条件 :一旦 path 的长度大于或等于 2,就将其加入到结果集合中。

  3. 遍历选择过程

    • start 位置开始遍历数组 nums

    • 若当前元素小于 path 中的最后一个元素,说明不满足递增条件,直接跳过。

    • 检查当前元素是否在本层已经使用过,如果是则跳过,以此实现去重

    • 若满足条件,将当前元素加入 path,然后递归进入下一层继续构建子序列。

去重技巧说明

由于本题不能对数组进行排序(排序会破坏元素原有顺序),所以采用 set 记录本层已使用元素的方法来实现去重,这种方式适用于未排序数组的情况。

代码实现:

javascript 复制代码
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var findSubsequences = function (nums) {
  const result = [];// 结果集
  const path = [];// 路径集
  const back = function (start) {
    if (path.length > 1) {
      result.push([...path]);
    }
    // 必须从左到右
    const set = new Set();
    for (let i = start; i < nums.length; i++) {
      // 同一层不能重复 应该判断当前层是否已经使用过该元素 
      if (i > start && set.has(nums[i])) continue;
      if (path.length === 0 || nums[i] >= path[-1]) {
        path.push(nums[i]);
        back(i + 1);
        // 回溯
        path.pop();
        //标记
        set.add(nums[i]);
      }
    }
  };
  back(0);
  return result;
};

复杂度分析

  1. 时间复杂度 :在最坏情况下,每个元素都有选或不选两种状态,因此时间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 2 n ) O(2^n) </math>O(2n),其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n 为数组 nums 的长度。

  2. 空间复杂度 :由于递归栈的深度最大为数组长度 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n,所以空间复杂度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)。

关键总结

  1. 递增子序列构建:始终保证每次选择的元素大于或等于当前子序列的最后一个元素。

  2. 去重策略 :利用 set 记录本层已使用的元素,有效避免生成重复子序列。

  3. 回溯框架运用 :严格遵循 "选择 → 递归 → 撤销选择" 的流程,通过 start 控制遍历范围,确保算法正确且高效地运行。

相关推荐
天若有情67332 分钟前
TFword:从字符到片段,解析一个“小而精”的字符串处理工具的设计智慧
java·jvm·算法
DuHz1 小时前
频率分集阵列雷达——论文阅读
论文阅读·算法·汽车·信息与通信·毫米波雷达
机器学习之心1 小时前
198种组合算法+优化RF随机森林+SHAP分析+新数据预测!机器学习可解释分析,强烈安利,粉丝必备!
算法·随机森林·机器学习·shap分析·198种组合算法
jikiecui2 小时前
信奥崔老师:实数型: float、double
c++·算法
知花实央l2 小时前
【数字逻辑】数字逻辑实验实战:74HC151实现逻辑函数+74HC138搭全加器(附接线步骤+避坑指南)
算法·容器·测试用例·逻辑回归
CoovallyAIHub2 小时前
突破性开源模型DepthLM问世:视觉语言模型首次实现精准三维空间理解
深度学习·算法·计算机视觉
WaWaJie_Ngen2 小时前
【OpenGL】模板测试(StencilTest)
c++·算法·游戏·游戏引擎·游戏程序·图形渲染
Yuroo zhou3 小时前
破空驭风,智领未来 --5KG物流配送无人机展示飞行!
人工智能·算法·机器人·硬件工程·无人机
CoovallyAIHub3 小时前
ICCV 2025 最佳论文出炉:CMU 团队用「AI 积木大师」BrickGPT 摘得桂冠!
深度学习·算法·计算机视觉