LIS(Longest Increasing Subsequence)是比较典型的一个问题,比较容易想到的是**动态规划**解法,时间复杂度是**O(N^2)。**
先看一下题目,很容易理解:
示例:
输入:8
** **[10,9,2,5,3,7,101,18]
输出:4
解释:第一行输入元素个数,第二行输入元素序列。输出最长子序列的长度4.
说明:
- 子序列不是子串,子串是连续的,子序列不一定连续。
- 满足最长子序列不一定唯一,题目要求的是输出最长子序列的长度,而不是输出最长序列。
动态规划解法
这题设计使用动态规划算法,我们需要一个**dp数组**(定义的dp数组是可以本质上或者侧面解决实际问题的),假设dp[0...i-1]都已经算出来了,然后反问自己**如何算出的dp[i]**。
根据题目所求,我们可以这样**定义dp数组:**dp[i]表示以输入数组中**nums[i]这个数结尾**的最长递增子序列的长度。
举两个dp数组的实际意义的例子:
我们知道dp[i]是表示以输入元素结尾的最长递增子序列的长度后,我们就可以根据这个定义直接求出最终结果(子序列的最长长度)**等价于求dp数组中元素的最大值**。
int maxLen = 0;
for(int i=0;i<dp.size();++i){
maxLen = max(dp[i],maxLen);
}
return maxLen;
如何设计算法逻辑去求这个dp数组呢?
这里我们可以采用数学归纳的方法去实现状态转移。
我们在刚刚给的实例中加上个nums[5]:
如果我们清楚nums[0..4]元素对应的dp[0...4]的大小,我们清楚dp[i]是表示以nums[i]元素结尾的最长增长序列,那么我们**要求nums[5]对应的dp[5],是不是可以直接拿nums[5]和nums[0...4]比较大小,如果nums[5]大,那么dp[5]是不是就在相应的dp[0...4]加1呢。自此会有很多个子序列长度,但是我们要求的是最长子序列的长度,所以我们选择对应加1后最长的子序列就可以了。**
//把j当作0...4,i当作5,就更好理解了
for(int j=0;j<i;++j){
if(nums[i]>nums[j]&&dp[i]<dp[j]+1)//因为要求的是最长子序列长度,如果对应的dp[j]+1没有dp[i]
大,那dp[i]保持不变。
dp[i] = dp[j] + 1;
}
根据上面的逻辑关系然后以此类推,大致思想就构建完成了
注:根据dp数组的定义,我们需要给dp数组中元素初始化为1,因为序列长度本身为1。
根据算nums[5]对应的dp[5]思想可以算其他的元素对应的dp[i]:
for(int i=0;i<nums.size();++i){
for(int j=0;j<i;++j){
if(nums[i]>nums[j]&&dp[i]<dp[j]+1)
dp[i] = dp[j] + 1;
}
}
接下来合并一下就可以写出完整代码了:
#include<bits/stdc++.h>
using namespace std;
int lisLength = 0;//最长递增子序列长度
void lengthOfLis(vector<int> nums,vector<int> dp){
for(int i=0;i<nums.size();++i){
for(int j=0;j<i;++j){
if(nums[i]>nums[j]&&dp[i]<dp[j]+1)
dp[i] = dp[j] + 1;
}
}
for(int k=0;k<nums.size();++k){
lisLength = max(lisLength,dp[k]);
}
}
int main(){
int n;
cin>>n;
vector<int> nums(n);
vector<int> dp(n,1); //dp数组中元素赋值为1
for(int i=0;i<n;++i)
cin>>nums[i];
lengthOfLis(nums,dp);
cout<<lisLength<<endl;
return 0;
}
时间复杂度O(N^2).
版权归原作者 ·ᴥ· 所有, 如有侵权,请联系我们删除。