算法提高——动态规划练习
最长不下降子序列
一、问题描述
现在给出一个数字序列(整数),要求找出它的一个最长不下降子序列,返回这个子序列的长度,即该数字中最长的非递减子序列,元素可以不是连续的,但要满足元素间的前后位置关系。
例如:序列A={1,2,2,-1,-2,7,9},则A的最长不下降子序列为{1,2,2,7,9}
二、问题分析
穷举,找出数组的所有子序列对于四个元素的数组,所有可能的子序列有1+4+6+4 +1=C40+C41……C44。显然穷举法时间复杂度为2^n
分析穷举法可以发现,在穷举子序列的时候出现了重复的子序列,比如{1,2,3,4}这个数组的子序列中{1,2,3}中出现了子序列{1,2},{1},所以进行了重复遍历从而使得时间复杂度增加。
动态规划思想,在观察第n个元素时,可以先观察前n-1个元素的最长不下降子序列,然后在次基础上求n个元素的最长不下降子序列。所以,可以递推的求得一个最长不下降子序列,要求n个元素的先求n-1个元素的,为了方便理解以下举个例子来说明
假设序列{1,-2,4,6,3,15},现在求该序列的最长不下降子序列,从头开始遍历写出遍历过程
遍历到1,该元素是第一个元素,1个元素的最长不下降子序列就是自己,标记长度1;
遍历到-2,该元素值小于前一个元素,所以不能构成不下降子序列{1,-2}的最长不下降子序列为{1},标记长度1
遍历到4,该元素值比1大,1的标记长度为1,该元素值比-2大,-2的标记长度为1,所以4的标记长度=max(1+1,1+1)
遍历到6,该元素值比1大,1的标记长度为1,该元素的值比-2大,-2的标记长度为1,该元素值比4大,4的标记长度为2,所以6的标记长度为max(1+1,1+1,2+1)
遍历到3,3>1,length1=1,3>-2,length-2=1,3<4,3<6,所以length3=max(1+1,1+1)
遍历到15,与上述同理
三、算法分析
通过对问题的分析,已经有了初步的规划思路,当遍历到第i个元素时,让value(i)和前i-1个元素进行比较,如果i的value值更大则对i的标记长度作更新,如果i的value值小,那么就不作更新。具体的比较算法为:if A[i]>A[j],dp[i]=max(dp[i],dp[j]+1);else do nothing。
dp[i]记录的是以A[i]作为结尾元素的所有子序列中的最长不下降子序列的长度,这个必须理解,否则就无法理解动态规划算法。
而比较的过程,其实是在尝试把A[i]加入到{1……i-1}这个数组的子序列中,找出最长的非递减子序列,因为根据递推关系,{1……i-1}这个序列的最长子序列一定以该序列中的某一元素结尾,而A[i]如果比这个结尾元素大,那么就可以扩充最长子序列,而此时相当于把元素i加入了{1……i-1}中,并且把{1……i}这个序列的最长非递减子序列长度记录到了dp[i]中。
上面这段分析请读者结合具体例子自己分析,这样有助于理解。
四、代码
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 const int N=100; 5 int A[N],dp[N]; 6 int main(){ 7 int n;//n为元素个数 默认小于100,可令加判断 8 scanf("%d",&n); 9 for(int i=0;i<n;i++){ 10 scanf("%d",&A[i]); 11 } 12 int ans = 0;//用作返回最大非递减子序的长度 13 for(int i=0;i<n;i++){ 14 dp[i]=1;//一个元素的最大非递减子序长度为1 15 for(int j=0;j<i;j++){ 16 if(A[i]>=A[j]){ 17 dp[i] = max(dp[j]+1,dp[i]); 18 } 19 } 20 ans = max(ans,dp[i]); 21 } 22 printf("%d",ans); 23 return 0; 24 }

浙公网安备 33010602011771号