动态规划题型总结

来源:互联网 发布:终极算法 pdf 编辑:程序博客网 时间:2024/05/17 04:40

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

动态规划特征:

(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、字符串对齐问题:两个字符串中相同的字符串彼此对应的最小花费?

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