快速解决最长递增子序列问题

      LIS(Longest Increasing Subsequence)是比较典型的一个问题,比较容易想到的是动态规划解法,时间复杂度是O(N^2)。

      先看一下题目,很容易理解:

示例

输入:8

           [10,9,2,5,3,7,101,18]

输出:4

解释:第一行输入元素个数,第二行输入元素序列。输出最长子序列的长度4.

说明:

  1. 子序列不是子串,子串是连续的,子序列不一定连续。
  2. 满足最长子序列不一定唯一,题目要求的是输出最长子序列的长度,而不是输出最长序列。

动态规划解法

      这题设计使用动态规划算法,我们需要一个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).

  • 27
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假正经的小柴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值