一、题目描述
给定一个整数数组 nums ,找到其中 最长严格递增子序列 的长度。
子序列定义:
子序列是从原数组中删除(或不删除)一些元素后,保持原有顺序得到的序列。
例如:
原数组:
[0,3,1,6,2,2,7]
合法子序列:
[3,6,2,7]
[0,1,2,7]
示例 1
输入:
nums = [10,9,2,5,3,7,101,18]
输出:
4
解释:
最长递增子序列为:
[2,3,7,101]
示例 2
输入:
nums = [0,1,0,3,2,3]
输出:
4
示例 3
输入:
nums = [7,7,7,7,7]
输出:
1
二、解法一:动态规划(DP)
1 思路分析
定义状态:
dp[i] 表示:
以 nums[i] 结尾的最长递增子序列长度
初始化:
dp[i] = 1
因为每个元素本身就是一个长度为1的序列。
状态转移
遍历 j < i:
如果:
nums[j] < nums[i]
说明可以接在后面:
dp[i] = max(dp[i], dp[j] + 1)
最终答案
max(dp[i])
2 动态规划图解
示例:
nums = [10,9,2,5,3,7,101,18]
DP 变化:
10 -> 1
9 -> 1
2 -> 1
5 -> 2
3 -> 2
7 -> 3
101 -> 4
18 -> 4
最长为:
4
3 C语言实现
#include <stdlib.h>
int max(int a,int b)
{
return a>b?a:b;
}
int lengthOfLIS(int* nums, int numsSize)
{
int *dp = (int*)malloc(sizeof(int)*numsSize);
int ans = 1;
for(int i=0;i<numsSize;i++)
dp[i] = 1;
for(int i=1;i<numsSize;i++)
{
for(int j=0;j<i;j++)
{
if(nums[j] < nums[i])
{
dp[i] = max(dp[i], dp[j] + 1);
}
}
ans = max(ans, dp[i]);
}
free(dp);
return ans;
}
时间复杂度
时间复杂度:O(n²)
空间复杂度:O(n)
当 n <= 2500 时可以通过。
三、解法二:贪心 + 二分查找(最优解)
这是 面试最推荐的方法。
复杂度:
O(n log n)
1 核心思想
维护一个数组:
tails[i]
表示:
长度为 i+1 的递增子序列的最小结尾元素
关键点:
tails 数组 不是实际的子序列,只是用于维护最优状态。
2 过程演示
示例:
nums = [10,9,2,5,3,7,101,18]
过程:
10
9
2
2 5
2 3
2 3 7
2 3 7 101
2 3 7 18
最终长度:
4
3 二分查找更新
每次处理 nums[i]:
在 tails 中找到:
第一个 >= nums[i] 的位置
替换:
tails[pos] = nums[i]
如果没找到:
追加到末尾
4 C语言实现
#include <stdlib.h>
int lengthOfLIS(int* nums, int numsSize)
{
int *tails = (int*)malloc(sizeof(int)*numsSize);
int size = 0;
for(int i=0;i<numsSize;i++)
{
int left = 0;
int right = size;
while(left < right)
{
int mid = (left + right) / 2;
if(tails[mid] < nums[i])
left = mid + 1;
else
right = mid;
}
tails[left] = nums[i];
if(left == size)
size++;
}
free(tails);
return size;
}
四、算法过程示例
输入:
nums = [0,1,0,3,2,3]
过程:
0
0 1
0 1
0 1 3
0 1 2
0 1 2 3
最终:
LIS = 4
五、两种方法对比
| 方法 | 时间复杂度 | 空间复杂度 | 难度 |
|---|---|---|---|
| 动态规划 | O(n²) | O(n) | ⭐ |
| 贪心 + 二分 | O(n log n) | O(n) | ⭐⭐⭐ |
六、面试常见问题
1 什么是 LIS?
最长严格递增子序列
不要求连续
但顺序不能改变
2 为什么可以用二分?
因为:
tails数组始终保持递增
所以可以:
二分查找插入位置
3 tails 数组是不是 LIS?
不是。
tails只是维护最优尾值
例如:
tails = [2,3,7,18]
真实 LIS 可能是:
[2,5,7,101]
七、总结
解决 LIS 的核心方法:
方法一
动态规划
O(n²)
思路清晰,容易理解。
方法二(推荐)
贪心 + 二分查找
O(n log n)
面试中更常考。