最长子序列问题——动态规划算法初解

引出:

首先我们拿北京大学ACM在线测试系统题库中的一道题来做为例子,然后我们在解题的过程中初步接触下动态规划(DP)算法,原题地址:

http://poj.org/problem?id=2533

原题是英文的,简单翻译一下:

给出一个序列a1,a2,a3,a4,a5,a6,a7….an,求它的一个子序列(设为i1,i2,…in),使得这个子序列满足这样的性质,i1<i2<i3<…<in并且这个子序列的长度最长,输出这个最长的长度。比如一个序列(1, 7, 3, 5, 9, 4, 8)有很多个子序列比如(1, 7), (3, 4, 8),但是它的最长子序列是(1, 3, 5, 8)或(1, 3, 5, 9),长度为4(这是递增情况,同理递减也为同一类问题,不多做赘述)。


动态规划基本思想将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。经分解得到子问题往往不是互相独立的,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。


分析:

分析上述这个问题,使用动态规划是有效的方法,我们可以把求最长子序列串的问题,分解为求长度为n-1序列最长子序列串的子问题,依次分解成多个子问题,接下里我们详细来看一下算法步骤,首先我们用一个数组num[]来存放序列:

num[] = {1,7,3,5,9,4,8}

同时用另一个同样长度的数组len[]来存放,以num中对位数为结尾的最长子序列长度,初始值为1(因为自身长度即为1)

len[] = {1,1,1,1,1,1,1}

然后我们使用双重循环从低位开始遍历,第一轮拿num[0]与num[1]比较,因为1<7,并且len[0]<=len[1],说明已num[1]为尾的序列长度比以num[0]的序列长1,所以len[1]=len[0]+1,第一轮遍历后len数组变为

len[] = {1,2,1,1,1,1,1}

第二轮我们依次拿num[0]和num[1],与num[2]比较,遍历完后len变为

len[] = {1,2,2,1,1,1,1}

依次循环,直至序列结尾,我们把所有轮次len的变化列出来,方便大家查看变化,如下:

1,2,1,1,1,1,1————第1轮遍历

1,2,2,1,1,1,1————第2轮遍历

1,2,2,3,1,1,1————第3轮遍历

1,2,2,3,4,1,1————第4轮遍历

1,2,2,3,4,3,1————第5轮遍历

1,2,2,3,4,3,4————第6轮遍历


由上表格,我们可以清晰的看出len数组体现的动态规划思想,将前边计算得出的结论存放,在后续问题中用到的时候直接从存放的数据中读取,而不需要做重复的运算,大大降低了冗余运算量。

至此,希望这篇文章能够让大家对动态规划有一个初步的认识,余后可以去了解下更复杂的场景,最后附上我解答那道题的java源码(源码是基于北京大学在线评测系统Problem2533输入格式所写,大家可将输入的部分替换为直接赋值)。


源码:

[java]  view plain  copy
  1. import java.io.PrintWriter;  
  2. import java.util.Scanner;  
  3.   
  4. public class Test2533_DP {  
  5.     static Scanner cin = new Scanner(System.in);  
  6.     static PrintWriter cout = new PrintWriter(System.out, true);  
  7.     public static void main(String[] args)   
  8.     {  
  9.         int n = cin.nextInt();//获取序列长度  
  10.         int[] num = new int[n];  
  11.         int[] len = new int[n];  
  12.         int max = 1;  
  13.         for(int i=0;i<n;i++){  
  14.             num[i] = cin.nextInt();//依次输入序列数字,空格相隔   
  15.             len[i] = 1;  
  16.         }  
  17.         for(int i=1;i<n;i++){  
  18.             for(int j=0;j<i;j++){  
  19.                 if(num[i]>num[j] && len[i]<len[j]+1)  
  20.                     len[i] = len[j] + 1;  
  21.             }  
  22.             if(max<len[i])  
  23.                 max = len[i];  
  24.         }  
  25.         cout.println(max);  
  26.     }  
  27. }  
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值