线性动态规划总结–最长上升子序列
线性动态规划的相关问题主要有以下几个分类:
- 最长上升子序列(LIS)和最长下降子序列
- 最长字段和
- 最长公共子序列(LCS)
1.最长上升子序列
首先讲一下子序列和子串的区别:
假设给定“acdefsh"
- 子序列:“adfs”,“cefh”,“ce”,字符串不需要连在一起,但是字符串之间的相对位置不能改变。
- 子串:“acde",“defs”,“sh”,字符串必须连在一起
最长上升子序列,顾名思义,就是在一组序列中,最长的并且呈上升趋势的子序列。例如给定序列a1,a2,a3,a4,并且a1<a2<a3<a4,那么称这个序列为上升的。给定序列(a1=2,a2=4,a3=1,a4=5,a5=2),有上升子序列(a1,a2,a4),(a2,a4),(a3,a5),但是最长上升子序列是(a1,a2,a4)。
最长上升子序列可以有很多个,但是长度一定是固定的。
最长上升子序列相关的题目主要有三种做法,下面主要写一下其中两种:
- 使用dp动态规划
- 使用贪心+二分的思想
1.DP动态规划:
代码:
#include<bits/stdc++.h>
using namespace std;
int N[100];
int dp[100];
int M;
int ans=1;
int main()
{
cin>>M;
for(int i=1;i<=M;i++)
cin>>N[i];
for(int i=1;i<=M;i++)
dp[i]=1;
for(int i=1;i<=M;i++)
{
for(int j=1;j<i;j++)
{
if(N[j]<N[i])
{
dp[i]=max(dp[j]+1,dp[i]);
ans=max(ans,dp[i]);
}
}
}
cout<<ans<<endl;
}
假设给定一组长度N为5的序列(1,4,2,7,6),求出给定序列的最长上升子序列。
首先开一个一维数组dp[6],dp[i]表示在以第i个数为结尾的状态下,最长上升子序列是多少。例如:dp[2]=2表示在给定序列中以第二个数4为结尾时,最长上升子序列是2,因为4前面有个1,最后,找出dp[i]中最大的数,这个数就是这个序列的最长上升子序列。
状态转移方程:
dp[i]=max(dp[j]+1,dp[i])
//以第i个数为结尾时的最长上升子序列
那么就有以下的情况:
i=1:
dp[1]=1
i=2:
j=1:N[1]<N[2]
dp[2]=max(dp[1]+1,dp[2])=max(1+1,1)=2
i=3:
j=1:N[1]<N[3]:
dp[3]=max(dp[1]+1,dp[3])=max(1+1,1)=2。
j=2:N[2]>N[3]:
直接跳过。
i=4:
j=1:N[1]<N[4]:
dp[4]=max(dp[1]+1,dp[4])=max(1+1,1)=2
j=2:N[2]<N[4]:
dp[4]=max(dp[2]+1,dp[4])=max(2+1,2)=3
j=3:N[3]<N[4]:
dp[4]=max(dp[3]+1,dp[4])=max(2+1,3)=3
i=5:
j=1:N[1]<N[5]:
dp[5]=max(dp[1]+1,dp[5])=max(1+1,1)=2
j=2:N[2]<N[5]:
dp[5]=max(dp[2]+1,dp[5])=max(2+1,2)=3
j=3:N[3]<N[5]:
dp[5]=max(dp[3]+1,dp[5])=max(2+1,3)=3
j=4:N[4]>N[5]:
直接跳过
2.贪心+二分
代码:
#include<bits/stdc++.h>
using namespace std;
int N[100];
int low[100];
int K;
int m_len;
void Find(int x,int l,int r)
{
int mid=0;
while(l<=r)
{
mid=(l+r)/2;
if(x>=low[mid])
{
l=mid+1;
}
else if(x<low[mid])
{
r=mid-1;
}
}
low[mid]=x;
}
int main()
{
cin>>K;
for(int i=1;i<=K;i++)
cin>>N[i];
for(int i=1;i<=K;i++)
{
if(N[i]>low[m_len])
low[++m_len]=N[i];
else
Find(N[i],1,m_len);
}
cout<<m_len<<endl;
}
在这个方法中,我们使用一个辅助数组low[m_len]来帮忙。其实从上一个方法可以观察到,dp[j](以N[j]为尾数的最长上升子序列)是在所有满足N[t]<N[i] (1<=t<i ) 的dp[1]到dp[i-1]中最大的一个数。所以low[m_len]就记录了这样一种关系,m_len表示当前序列的最长上升子序列,low[i](0<=i<=m_len)表示在所有上升子序列为i的情况下,第i个数的最小值。例如,一个序列有两个长度为3的上升序列:(1,5,7)和(2,3,6),low[3]=6,因为6是这两个长度为3的上升子序列的第3个数的最小值。因此,就能够保证low[m_len]在所有长度为m_len的最长上升子序列的情况下,是所有第m_len个数的最小值。同时,low数组是一个单调上升的序列。
如果N[i+1]>low[m_len],low[++m_len]=N[i+1]。因为N[i+1]比当前最长上升子序列的最大值还大,所以m_len加上1,即最长上升子序列加1,长度为m_len的上升子序列的最大数也赋值了N[i+1]。
如果N[i+1]<=low[m_len],那么就找到第一个>=N[i+1]的数,然后替换掉它。例如,low[4]=(1,4,5,8), N[i+1]=3, N[i+1]<low[4], low[2]=4是第一个大于N[i+1]的数,所以low[2]=N[i+1]=3,意味着长度为在所有长度为2的上升子序列中,3是结尾最小的数。因为low数组是一个单调上升的序列,所以可以用二分来查找这个数。
假设有一组长度为N=7的序列:1 5 3 6 4 2 6求最长上升子序列。
i=1, m_len=0:
N[1]>low[m_len], low[++m_len]=N[1]=1
low[1]={1}
i=2,m_len=1:
N[2]>low[m_len],low[++m_len]=N[2]=5
low[2]={1,5}
i=3,m_len=2:
N[3]<=low[m_len], low[2]=N[3]=3 (因为low[2]是第一个比N[3]大的数)
low[2]={1,3}
i=4,m_len=2:
N[4]>low[m_len],low[++m_len]=N[4]=6
low[3]={1,3,6}
i=5,m_len=3:
N[5]<=low[m_len], low[3]=N[5]=4 (因为low[3]是第一个比N[5]大的数)
low[3]={1,3,4}
i=6,m_len=3:
N[6]<=low[m_len], low[2]=N[6]=2 (low[2]是第一个比N[6]大的数)
low[3]={1,2,4}
i=7,m_len=3:
N[7]>low[m_len], low[++m_len]=N[7]=6
low[4]={1,2,4,6}
最后输出m_len=4,最长上升子序列为4