动态规划题型总结

三个重要概念:最优子结构边界状态转移方程

动态规划特征:

(1)最优子结构:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。意思就是,总问题包含很多个子问题,而这些子问题的解也是最优的。

(2)重叠子问题:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

1、台阶问题:有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或2级台阶,要求用程序来求出一共有多少种走法?

解法:设f(10)为到第10阶台阶的走法数,那么f(10)=f(8)+f(9), f(8)和f(9)即为f(10)的最优子结构, 可以看出是一个斐波那契数列,更一般地,f(i) = f(i-2)+f(i-1),边界值f(1)=1,f(2)=2,那么状态转移方程如下:

f(1) = 1,

f(2) = 2,

f(n) = f(n-1) + f(n-2)     (n>=3).

分析:用递归方法求解时,在递归树上有很多结点是重复的,而且重复的结点数会随着n的增大而急剧增大,计算量也会随之增大,所以用递归方法的时间复杂度是以n的指数的方式递增的。所以不能使用递归求解,优化的方向是怎样避免计算重复值,所以更好的方法是自底向上计算,这样前面计算过的就不用再重复计算了。

代码实现:


  
  
1
2
3
4
5
6
7
8
9
10
11
12
public  int  Fibonacci( int  n){
     if( n == 1return  1;
     if( n == 2return  2;
     int  fibOne  =  1, fibTwo  =  2;
     int  fibN  =  0;
     for( int  i = 3; i <= n; i ++){
       fibN  =  fibOne  +  fibTwo;
       fibOne  =  fibTwo;
       fibTwo  =  fibN;
    }
     return  fibN;
}

时间复杂度:O(n)

2、变态台阶问题:有n阶台阶,一只青蛙可以跳上1级台阶,也可以跳上2级台阶...也可以跳上n级,此时青蛙跳上一个n级的台阶总共有多少种跳法?

解法:设f(n)为青蛙跳上n阶台阶的跳法数,f(4-1)为4阶台阶下有一次跳1阶台阶的跳法数,可以知道四级台阶下有一次选择跳1阶的跳法数f(4-1)等于3级台阶的所有跳法数f(3),所以f(4-1)=f(3),那么

f(0) = 1;

f(1) = 1;

f(2) = f(2-1)+f(2-2) = f(1)+f(0);

f(3) = f(3-1)+f(3-2)+f(3-3) = f(2)+f(1)+f(0)

.....

f(n) = f(n-1)+ f(n-2)+...+f(2)+f(1) +f(0)         (1)  (n>=0)

f(n-1) =  f(n-2) +f(n-1)+...+f(2)+f(1) +f(0)     (2)  (n>=1)

(1)-(2)得到:f(n) = 2f(n-1)=4f(n-2)=..=2^(n-1)f(1)=2^(n-1)   (n>=1)

代码实现: 


  
  
public  int  JumpFloorII( int  target) {
     int  sum  =  1;
     for( int  i = 1; i < n; i ++)
         sum  *  =  2;
     return  sum;
}

时间复杂度:O(n)

3、最长递增子序列问题:给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4.

解法1:设数组为array,以array[j]结尾的最长递增子序列为L[j],那么L[j] = max(L[i])+1, 其中 0=<i<j,array[j]>array[i],L[0]=1,这样求得所有L[j](0<=j<=array.length-1)后,比较所有的L[j]可获得最长单调递增子序列的长度,如代码实现1所示。如果还需要求得最长递增子序列,需要用一个数组保存以每个元素i结尾的递增子序列,如果有多个最长递增子序列,暂时只能求得一个子序列,如代码实现2所示。

代码实现1:求最长单调递增子序列的长度。


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public  int  LIS( int[]  array){
     //array[j]L[j]
     int[]  L  =  new  int[ array. length];
     //L[j]
     L[ 0=  1//
     for( int  j = 1; j < array. length; j ++){
         int  maxL  =  0;
         for( int  i = 0; i < j; i ++){
             if( array[ j] > array[ i&&  L[ i] > maxL)
               maxL  =  L[ i];
        }
         L[ j=   maxL +1;
    }
     //L[j]
     int  max  =  L[ 0];
     for( int  j = 1; j < array. length; j ++){
         if( L[ j] > max)
            max  =  L[ j];
    }
     return  max;
}

时间复杂度:O(n^2)

代码实现2:求最长单调递增子序列的长度和一个最长递增子序列。


  
  
public  void  longestIncreSubSeq( int[]  array) {
int[]  L  =  new  int[ array. length];
// L[j],L[j]
int  max  =  L[ 0];
int  maxj  =  -1;
int[][]  lensArr  =  new  int[ array. length][ array. length]; // i
for ( int  i  =  0i  <  array. lengthi ++) {
L[ i=  1;
lensArr[ i][ 0=  array[ i];
}
for ( int  j  =  1j  <  array. lengthj ++) {
int  maxL  =  0;
int  maxi  =  -1// max(L[i])i
for ( int  i  =  0i  <  ji ++) {
if ( array[ j>  array[ i]) {
if ( L[ i>  maxL) {
maxL  =  L[ i];
maxi  =  i;
}
}
}
L[ j=  maxL  +  1;
if ( maxi  !=  -1) {
for ( int  k  =  0k  <  L[ maxi];  k ++)
lensArr[ j][ k=  lensArr[ maxi][ k];  // L[maxi]L[j]
lensArr[ j][ L[ j-  1=  array[ j];  // 
}
if ( L[ j>  max) {
max  =  L[ j];
maxj  =  j;
}
}
System. out. println( ""  +  max);
System. out. print( "");
for ( int  i  =  0i  <  maxi ++
System. out. print( lensArr[ maxj][ i+  " ");
}

4、最长公共子序列问题:求两个或多个已知数列最长的子序列。

解法:运用自底向上的填表格法编码,设x[1...m]和y[1...n],c[i,j]为x[1...i]和y[1..j]的最长公共子序列的长度,那么

c[i,j] = c[i-1,j-1]                    if x[i] = x[j]

c[i,j] = max{c[i-1,j],c[i,j-1]}  otherwise

最后用在表格中用标记回溯法返回所有的最长公共子序列。

表格例子: 

 

 

A

B

C

B

D

A

B

 

0

0

0

0

0

0

0

0

B

0

0

1

1

1

1

1

1

D

0

0

1

1

1

2

2

2

C

0

0

1

2

2

2

2

2

A

0

1

1

2

2

2

3

3

B

0

1

2

2

3

3

3

4

A

0

1

2

2

3

3

4

4

代码实现:


  
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//b
public  int[][]  LCS( String  xString  y){
int  xLen  =  x. length();
int  yLen  =  y. length();
int[][]  b  =  new  int[ xLen +1][ yLen +1];
int[][]  c  =  new  int[ xLen +1][ yLen +1];
for( int  i = 0; i <= xLen; i ++)
c[ i][ 0=  0;
for( int  j = 0; j <= yLen; j ++)
c[ 0][ j=  0;
for( int  i = 1; i <= xLen; i ++){
for( int  j =  1; j <= yLen; j ++)
if( x. charAt( i -1) == y. charAt( j -1)) {
c[ i][ j=  c[ i -1][ j -1] +1;
b[ i][ j=  1;
}
else  if( c[ i -1][ j] >  c[ i][ j -1]){
c[ i][ j=  c[ i -1][ j];
b[ i][ j=  0;
}
else{
c[ i][ j=  c[ i][ j -1];
b[ i][ j=  -1;
}
}
return  b;
}
//
public  void  Display( int[][]  bString  xint  iint  j){
if( i == 0 || j == 0)
return;
if( b[ i][ j] == 1){
Display( bxi -1j -1);
System. out. print( x. charAt( i -1) + " ");
} else  if( b[ i][ j] == 0)
Display( bxi -1j);
else  if( b[ i][ j==  -1)  
Display( bxij -1);
}

时间复杂度:O(m*n)

5、连续子数组和最大问题:一个整数数组中的元素有正有负,在该数组中找出一个连续子数组,要求该连续子数组中各元素的和最大,这个连续子数组便被称作最大连续子数组。比如数组{2,4,-7,5,2,-1,2,-4,3}的最大连续子数组为{5,2,-1,2},最大连续子数组的和为5+2-1+2=8。

解法:设数组为array,以array[i]结尾的最大连续子数组和为dp[i],那么dp[i]=dp[i-1]>0?dp[i-1]:0+array[i],其中0<=i<=array.length-1,dp[0]=array[0],最后比较所有的dp[i],其中最大的一个即为最大连续子数组和。

代码实现:


  
  
public  int  maxSubArray( int[]  array){
     int[]  dp  =  new  int[ array. length];
     dp[ 0=  array[ 0];  //
     int  maxSum  =  dp[ 0];
     for( int  i = 1; i < array. length; i ++){
         dp[ i=  dp[ i -1] > 0? dp[ i -1]: 0 + array[ i];  //
         maxSum  =  Math. max( dp[ i], maxSum);
    }
     return  maxSum;
}

时间复杂度:O(n)

6、背包问题:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。

7、股票问题:3种变形

8、编辑距离问题:求两个字串之间,由一个转成另一个所需的最少编辑操作次数。编辑操作包括替换、插入、删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。

9、字符串对齐问题:两个字符串中相同的字符串彼此对应的最小花费?

注:代码均为手写输入,可能有错,其他动态规划问题未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值