求解线性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 }