求解线性dp的序列问题

一.最长上升子序列(LIS)

LIS(Longest Increasing Subsequence)最长上升子序列 
一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。

对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),

这里1 <= i1 < i2 < … < iK <= N。 
比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。

这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8). 
你的任务,就是对于给定的序列,求出最长上升子序列的长度。

两种求解方法:

one:O(n^2):

状态设计:dp[i]代表以a[i]结尾的LIS的长度 
状态转移:dp[i]=max(dp[i], dp[j]+1) (0<=j< i, a[j]< a[i]) 
边界处理:dp[i]=1 (0<=j< n) 

代码:

 1 int LIS()
 2 {
 3     int ans=1;
 4     for(int i=1;i<=n;i++)
 5     {
 6         dp[i]=1;
 7         for(int j=1;j<i;j++)
 8         {
 9             if(a[i]>a[j])
10                 dp[i]=max(dp[i],dp[j]+1);
11         }
12     }
13     for(int i=1;i<=n;i++)
14         ans=max(ans,dp[i]);
15     return ans;
16 }

two:贪心+二分

a[i]表示第i个数据。 
dp[i]表示表示长度为i+1的LIS结尾元素的最小值。 
利用贪心的思想,对于一个上升子序列,显然当前最后一个元素越小,越有利于添加新的元素,这样LIS长度自然更长。 
因此,我们只需要维护dp数组,其表示的就是长度为i+1的LIS结尾元素的最小值,保证每一位都是最小值,

这样子dp数组的长度就是LIS的长度。

dp数组具体维护过程同样举例讲解更为清晰。 
同样对于序列 a(1, 7, 3, 5, 9, 4, 8),dp的变化过程如下:

  • dp[0] = a[0] = 1,长度为1的LIS结尾元素的最小值自然没得挑,就是第一个数。 (dp = {1})
  • 对于a[1]=7,a[1]>dp[0],因此直接添加到dp尾,dp[1]=a[1]。(dp = {1, 7})
  • 对于a[2]=3,dp[0]< a[2]< dp[1],因此a[2]替换dp[1],令dp[1]=a[2],因为长度为2的LIS,结尾元素自然是3好过于7,因为越小这样有利于后续添加新元素。 (dp = {1, 3})
  • 对于a[3]=5,a[3]>dp[1],因此直接添加到dp尾,dp[2]=a[3]。 (dp = {1, 3, 5})
  • 对于a[4]=9,a[4]>dp[2],因此同样直接添加到dp尾,dp[3]=a[9]。 (dp = {1, 3, 5, 9})
  • 对于a[5]=4,dp[1]< a[5]< dp[2],因此a[5]替换值为5的dp[2],因此长度为3的LIS,结尾元素为4会比5好,越小越好嘛。(dp = {1, 3, 4, 9})
  • 对于a[6]=8,dp[2]< a[6]< dp[3],同理a[6]替换值为9的dp[3],道理你懂。 (dp = {1, 3, 5, 8})

这样子dp数组就维护完毕,所求LIS长度就是dp数组长度4。 
通过上述求解,可以发现dp数组是单调递增的,因此对于每一个a[i],先判断是否可以直接插入到dp数组尾部,

即比较其与dp数组的最大值即最后一位;如果不可以,则找出dp中第一个大于等于a[i]的位置,用a[i]替换之。 
这个过程可以利用二分查找,因此查找时间复杂度为O(logN),所以总的时间复杂度为O(N*logN)

 1 int LIS()
 2 {
 3     memset(dp,inf,sizeof(dp));
 4     int pos=0;
 5     dp[0]=a[0];
 6     for(int i=1;i<=n;i++)
 7     {
 8         if(a[i]>dp[pos])
 9             dp[++pos]=a[i];
10         else
11             dp[lower_bound(dp,dp+pos+1,a[i])-dp]=a[i];
12     }
13     return pos+1;
14 }

 

二.最长公共子序列(LCS)

在两个字符串中,有些字符会一样,可以形成的子序列也有可能相等,因此,长度最长的相等子序列便是两者间的最长公共字序列,其长度可以使用动态规划来求。

 1 int LCS()
 2 {
 3     memset(dp,0,sizeof(dp));
 4     for(int i=1;i<=n;i++)
 5     {
 6         for(int j=1;j<=n;j++)
 7         {
 8             if(a[i-1]==b[j-1])
 9                 dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
10             else
11                 dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
12         }
13     }
14     return dp[n][n];    
15 }

三.最长公共上升子序列(LCIS)

one:O(n^3)

解析:f[i][j]表示以b[j]结尾,字符串a[i]之前的公共上升子序列最大长度;
显然:f[i][j]>=f[i−1][j];;
递推:若a[i]!=b[j]:f[i][j]=f[i−1[j];
若a[i]==b[j]:f[i][j]=max(f[k][j])+1;(1<=k<=j-1&&b[j]>b[k])

 1 int LCIS()
 2 {
 3     int ans=0;
 4     for(int i=1;i<=n;i++)
 5     {
 6         for(int j=1;j<=n;j++)
 7         {
 8             if(a[i]!=b[j]) dp[i][j]=dp[i-1][j];
 9             else
10             {
11                 int maxx=0;
12                 for(int k=1;k<j;k++)
13                 {
14                     if(b[j]>b[k])
15                     maxx=max(maxx,dp[i-1][k]);
16                 }
17                 dp[i][j]=maxx+1;
18                 ans=max(dp[i][j],ans);
19             }
20         }
21     }
22     return ans;
23 }

 

two:O(n^2)

优化:通过记录对于当前a[i]与b[1-m]的匹配过程中的最大上升子序列mm,边记录,边更新,降低一维

 1 void LCIS()
 2 {
 3     for(int i=1;i<=n;i++)
 4     {
 5         int maxx=0;
 6         for(int j=1;j<=n;j++)
 7         {
 8             dp[i][j]=dp[i-1][j];
 9             if(a[i]>b[j])
10                 maxx=max(maxx,dp[i-1][j]);
11             if(a[i]==b[j])
12                 dp[i][j]=maxx+1;
13         }
14     }
15     int ans=0;
16     for(int i=1;i<=n;i++)
17         ans=max(ans,dp[n][i]);
18     return ans;
19 }

 

posted @ 2019-08-14 23:26  Spring-Onion  阅读(209)  评论(0编辑  收藏  举报