算法提高——动态规划练习

最长不下降子序列

一、问题描述

  现在给出一个数字序列(整数),要求找出它的一个最长不下降子序列,返回这个子序列的长度,即该数字中最长的非递减子序列,元素可以不是连续的,但要满足元素间的前后位置关系。

  例如:序列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 }

 

  

posted @ 2021-06-22 21:33  瑜琦  阅读(78)  评论(0)    收藏  举报