数据结构与算法自学系列之动态规划(一)

在刷了几次算法题都发现了动态规划的影子,一时之间竟无从下手,遂去网上查各种资料,希望能够慢慢弄懂这个算法思想。

首先从最简单的台阶问题来看,有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法?

从题目来看,以小白的想法,每次走台阶都有两种选择,如果用step(i)表示每一步走的步数,那么n = step(1) + step(2) + ... + step(i),step(i)可能值为1和2,首先考虑极值情况,全走1,需要走n次;如果n为偶数,全走2,需要走n/2次,所以i的范围在n/2到n之间,然而对n比较小的情况下,举个例子,n=5,i的可能情况就有8种,显然n越大值成几何倍数增加,这种情况,小白几乎就陷入瓶颈了,难受。

动态规划就是为了应对复杂状态情况下出现的算法,从名字就可以看出,为了应对动态的情况,对应这题,每次选择走1步还是2步,每次都需要我们做出动态抉择,直至到达题目给的限制条件,这里是到达n级台阶,动态规划分为从下至上或者从上至下两个角度,这题爬阶梯,我们先试着倒着分析,第n级台阶只能由n-1级台阶或者n-2级台阶上来,列出公式即为dp[n] = dp[n-1] + dp[n-2]。

C++代码如下:

#include<iostream>

#define N 20
using namespace std;

int dp[N];

int fun(int n) {
    if (n == 1 || n == 2) {
        return n;
    }
    dp[n - 1] = fun(n - 1);
    dp[n - 2] = fun(n - 2);
    dp[n] = dp[n - 1] + dp[n - 2];
    return dp[n];
}

int main() {
    fun(N);
    cout << dp[15] << endl;
    return 0;
}

其实这也是一个递归问题,不过这里用动态规划分析,下面看最短路径问题,这种问题求最值极值的,就要考虑一下是否可以用动态规划了。

给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和,如果给定的m如下,

1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0

那么路径1,3,1,0,6,1,0就是最小路径和,返回12。

同第一个题的分析方法,走到第(i,j)位置时,只可能是从(i-1,j)或是(i,j-1)过来的,状态转移方程即为dp[i][j] = a[i][j] + min(dp[i-1][j] + dp[i][j-1])。

C++代码如下:

扫描二维码关注公众号,回复: 5476302 查看本文章
#include<iostream>
#include<algorithm>

using namespace std;

int dp[4][4] = {};

int main(int argc,char** argv) {
    int a[4][4] = { 1,3,5,9,8,1,3,4,5,0,6,1,8,8,4,0 };
    for (int i = 0; i < 4;i++) {
        for (int j = 0; j < 4;j++) {
            if (i==0&&j==0) {
                dp[i][j] = a[i][j];             //出发初始位置
            }
            else if(i==0&&j!=0){
                dp[i][j] = a[i][j] + dp[i][j - 1];//上边界
            }
            else if (i != 0 && j == 0) {
                dp[i][j] = a[i][j] + dp[i - 1][j];//左边界
            }
            else {
                dp[i][j] = a[i][j] + min(dp[i - 1][j], dp[i][j - 1]);//动态规划选择右走还是往下走
            }
        }
    }
    cout << dp[3][3] << endl;
    return 0;
}

从代码中可以看到,两层循环遍历了整个数组,一层层把所有点到出发点的最短距离找到,接着看下剑指offer上的剪绳子问题,

给定一根长度为n的绳子,剪成m段(m,n>1都是整数),每段绳子长度记为k[0],k[1],k[2],…,k[m-1],求k[0]*k[1]*k[2]*…*k[m-1]乘积的最大值,例如:n=8,则可以得到剪出3段时,分别为2,3,3,乘积最大为18.

首先动态规划就是看某一步有多少种可能性,之前说了动态规划有从上到下和从下到上两种,这里只能选择从下到上,因为我们尚不清楚最后会剪成什么样,那么第一步剪绳子,有n-1种可能,f(n)=max(f(i)xf(n-i)),首先我们得到f(2)=1,f(3)=2,这两个作为基本(至于为什么是基本,大家可以自己推推后面几个),一直可以推出f(n)来,现在假如输入n=1000,我们不能从1000开始分解成m段,但是我们可以从第一段开始往上求,通过一层一层往上算,并将算的结果存在数组中,求到1000为止,最后就能知道结果了。

C++代码如下:

#include<iostream>
#include<algorithm>

using namespace std;

int maxProduct(int length) {
    if (length < 2) return 0;
    if (length == 2) return 1;
    if (length == 3) return 2;//初始绳子长度的特殊情况

    int product = 1;
    int max = 0;

    int *products = new int[length + 1];//存算好的值的数组
    products[0] = 0;
    products[1] = 1;

    products[2] = 2;                  //这里是剪出的绳子两个基本单位
    products[3] = 3;

    for (int i = 4; i <= length; ++i) { //这一层循环是将length以下的product[i]存到数组中
        for (int j = 1; j <= i/2; ++j) {//这一层循环是求剪一刀乘积的最大值
            product = products[j] * products[i - j];
            if (product > max) max = product;
        }
        products[i] = max;
    }
    return products[length];
}

int main() {
    int n = 7;
    cout << maxProduct(n) << endl;
}

当然这不是最优解法,只是在动态规划场景中运用所学的知识,有兴趣的可以深入学习。

猜你喜欢

转载自www.cnblogs.com/51selfstudy/p/10502277.html