1、什么是最长上升子序列?
最长上升子序列(Longest Increasing Subsequence),简称LIS,是指在一个序列中找到一个严格递增的子序列,其长度最大。例如,给定序列[10,9,2,5,3,7,101,18],则其最长上升子序列为[2,3,7,101],长度为4。
2、如何去写其状态转移方程?
LIS问题是一个经典的动态规划问题,其状态转移方程如下:
dp[i] = max(dp[i],dp[j] + 1),其中j是0到i-1之间的任意一个数,且nums[j] < nums[i]
意思是dp[i]表示以第i个数字结尾的最长上升子序列的长度。
2.1 初始值
初始时,dp数组中的每个元素都被设为1,因为单独一个数字也是一个长度为1的上升子序列。
2.2 状态转移方程
在计算dp[i]时,需要枚举i前面的所有数字j,如果nums[j] < nums[i],那么dp[i] = max(dp[i], dp[j] + 1),也就是选择第j个数字作为i的前一个数字时,以j结尾的最长上升子序列的长度+1就是以i结尾的最长上升子序列的长度。
2.3 最终结果
最终,dp数组中的最大值即为整个序列中的最长上升子序列长度。
3、可以手算一下:
序列:10, 9, 2, 5, 3, 7, 101, 18
初始化:dp[0]=dp[1]=dp[2]=dp[3]=dp[4]=dp[5]=dp[6]=dp[7]=1
计算dp[1]:枚举j=0,不满足nums[j] < nums[i](因为10>9),dp[1] =1
计算dp[2]:枚举j=0,不满足nums[j] < nums[i](因为10>2),dp[2] =1;枚举j=1,不满足nums[j] < nums[i](因为9>2),dp[2] =1
计算dp[3]:枚举j=0,不满足nums[j] < nums[3](因为10>5);枚举j=1,不满足nums[j] < nums[3](因为9>5);枚举j=2,满足nums[j] < nums[3](2<5),dp[3]=max(dp[3],dp[2]+1)=max(1,2)=2
计算dp[4]:枚举j=0,不满足nums[j] < nums[4](因为10>3);枚举j=1,不满足nums[j] < nums[4](因为9>3);枚举j=2,满足nums[j] < nums[4](因为2<3),dp[4] = max(dp[4], dp[2]+1) = 2;枚举j=3,不满足nums[j] < nums[4]
计算dp[5]:枚举j=0,不满足nums[j] < nums[5];枚举j=1,不满足nums[j] < nums[5];枚举j=2,满足nums[j] < nums[5](2<7),dp[5] = max(dp[5], dp[2]+1) = 2;枚举j=3,满足nums[j] < nums[5],dp[5] = max(dp[5], dp[3]+1) = 3;枚举j=4,满足nums[j] < nums[5](3<7),dp[5] = max(dp[5], dp[4]+1) = 3
计算dp[6]:枚举j=0,满足nums[j] < nums[6](10<101),dp[6] = max(dp[6], dp[0]+1) = 2;枚举j=1,满足nums[j] < nums[6](9<101),dp[6] = max(dp[6], dp[1]+1) = 2;枚举j=2,满足nums[j] < nums[6],dp[6] = max(dp[6], dp[2]+1) = 2;枚举j=3,5<101,dp[6] = max(dp[6], dp[3]+1) = 3;枚举j=4,3<101,dp[6] = max(dp[6], dp[4]+1) = 3;枚举j=5,7<101,dp[6] = max(dp[6], dp[5]+1) = 4
计算dp[7]:枚举j=0,10 <18,dp[7] = max(dp[7], dp[0]+1) = 2;枚举j=1,9 < 101,dp[7] = max(dp[7], dp[1]+1) = 2;枚举j=2,满足2 < 10,dp[7] = max(dp[7], dp[2]+1) = 2;枚举j=3,满足nums[j] < nums[7],5<18,dp[7] = max(dp[7], dp[3]+1) = 3;枚举j=4,3<18,dp[7] = max(dp[7], dp[4]+1) = 3;枚举j=5,7<18,dp[7] = max(dp[7], dp[5]+1) = 4;枚举j=6,不满足nums[j] < nums[7]
所以最后的结果是dp数组中的最大值,即4
4、给出C++代码实现:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n, 1); //初始化dp数组
int res = 1;
for(int i=1;i<n;i++) {
for(int j=0;j<i;j++) {
if(nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j]+1); //状态转移方程
}
}
res = max(res, dp[i]);
}
return res;
}
该算法的时间复杂度为O(n^2),空间复杂度为O(n)。
除了动态规划思路外,还有一种基于二分查找的算法,可以将时间复杂度缩减至O(nlogn),有兴趣的读者可以自行探索。