最长递增子序列(LIS)的 DFS 解法详解与实现
题目描述
给定一个整数数组(如 [10,9,2,5,3,7,101,18]
),计算其最长递增子序列的长度。其中,子序列不要求连续,仅需满足元素严格递增的顺序即可。
例如:
- 输入数组
[10,9,2,5,3,7,101,18]
- 最长递增子序列可为
[2,3,7,18]
或[2,5,7,101]
- 输出结果:
4
解题思路:深度优先搜索(DFS)
核心思想
最长递增子序列的本质是 “在每个元素位置做选择”——选当前元素(若满足递增条件,子序列长度 + 1)或不选当前元素(子序列长度不变)。通过深度优先搜索遍历所有可能的选择,记录其中的最大长度,即可得到答案。
关键参数设计
我们设计 dfs
函数包含三个核心参数,清晰描述当前搜索状态:
i
:当前遍历到的数组索引(表示 “正在处理第i
个元素”)last
:上一个被选入子序列的元素值(用于判断当前元素是否满足 “递增” 条件)len
:当前子序列的长度(实时记录当前路径的子序列长度)
递归逻辑
- 更新最大长度:每次进入递归函数,先判断当前子序列长度是否超过历史最大值,若超过则更新结果。
- 终止条件:当
i >= nums.size()
时(遍历完所有元素),直接返回,结束当前递归分支。 - 选择 1:选当前元素:若
nums[i] > last
(满足递增),则递归处理下一个元素,此时last
更新为nums[i]
,len
加 1。 - 选择 2:不选当前元素:无论是否满足递增,都可跳过当前元素,递归处理下一个元素,
last
和len
保持不变。
完整代码实现
#include <iostream>
#include <vector>
using namespace std;// 全局变量:存储输入数组和最终结果(最长递增子序列长度)
vector<int> nums;
int result = -1; // 初始化为-1,避免数组为空时逻辑错误/*** 深度优先搜索函数:遍历所有可能的子序列,计算最长递增子序列长度* @param i 当前遍历到的数组索引* @param last 上一个被选入子序列的元素值* @param len 当前子序列的长度*/
void dfs(int i, int last, int len) {// 每次递归先更新最大长度(确保所有可能的子序列长度都被考虑)if (result < len) {result = len;}// 终止条件:遍历完所有元素,结束当前分支if (i >= nums.size()) {return;}// 选择1:选当前元素(需满足递增条件)if (nums[i] > last) {dfs(i + 1, nums[i], len + 1);}// 选择2:不选当前元素(无论是否满足递增,都可跳过)dfs(i + 1, last, len);
}int main() {// 读取输入数组(支持空格分隔,回车结束输入)int a;cout << "请输入整数数组(空格分隔,回车结束):";while (cin >> a) {nums.push_back(a);// 检测到回车时停止输入if (cin.peek() == '\n') {break;}}// 初始调用DFS:从索引0开始,last初始为-1(表示暂无选中元素),初始长度为0dfs(0, -1, 0);// 输出结果cout << "最长递增子序列的长度为:" << result << endl;return 0;
}
代码测试与验证
测试用例 1:常规数组
- 输入:
10 9 2 5 3 7 101 18
- 输出:
4
- 说明:最长递增子序列为
[2,3,7,18]
或[2,5,7,101]
,长度均为 4。
测试用例 2:空数组
- 输入:(直接回车)
- 输出:
0
- 说明:空数组无元素,最长子序列长度为 0(代码中
result
初始为 - 1,但递归初始调用len=0
会更新result
为 0)。
测试用例 3:递减数组
- 输入:
5 4 3 2 1
- 输出:
1
- 说明:递减数组的最长递增子序列为单个元素,长度为 1。
测试用例 4:递增数组
- 输入:
1 2 3 4 5
- 输出:
5
- 说明:完整数组即为最长递增子序列,长度为 5。
算法分析
时间复杂度
- 复杂度:
O(2^n)
,其中n
为数组长度。 - 原因:每个元素有 “选” 和 “不选” 两种选择,递归会生成
2^n
个分支(实际因递增条件会减少部分分支,但最坏情况仍为O(2^n)
)。
空间复杂度
- 复杂度:
O(n)
。 - 原因:递归深度等于数组长度(最坏情况需遍历所有元素才终止),栈空间占用为
O(n)
;无额外辅助空间(全局变量不计入算法额外空间)。
优缺点与优化方向
优点
- 逻辑直观:直接模拟 “选或不选” 的决策过程,易于理解和实现。
- 无需预处理:直接对原始数组递归,代码简洁。
缺点
- 效率较低:
O(2^n)
的时间复杂度在n > 20
时会明显超时,无法处理大规模数组。
优化方向
若需处理更大规模的数组(如 n > 1000
),可改用以下更高效的算法:
- 动态规划(DP):时间复杂度
O(n^2)
,空间复杂度O(n)
(定义dp[i]
为以nums[i]
结尾的最长递增子序列长度)。 - 贪心 + 二分查找:时间复杂度
O(n log n)
,空间复杂度O(n)
(维护一个 “最小递增序列”,用二分查找优化更新过程)。
总结
本文通过深度优先搜索(DFS)实现了最长递增子序列的求解,核心是遍历 “选或不选” 的所有可能分支,记录最大子序列长度。该方法逻辑清晰,适合理解 LIS 问题的本质,但受限于 O(2^n)
的时间复杂度,仅适用于小规模数组。若需处理大规模数据,建议进一步学习动态规划或贪心 + 二分查找的优化方案