动态规划简单入门

0. 参考

本博客是看了视频后做的入门笔记,强烈建议去看一下,1个多小时就能入门。

1. 常见动态规划题目类型

  1. 计数:
  • 有多少种方式走到右下角
  • 有多少种方法选出k个数使得和为Sum
  1. 求最大最小值:
  • 从左上角走到右下角路径的最大数字和
  • 最长上升子序列长度
  1. 求存在性:
  • 取石子游戏,先手是否必胜
  • 能不能选出k个数使得和是Sum

2. 动态规划解题步骤

  1. 确定状态
  • 状态在动态规划中的作用属于定海神针;
  • 简单的说,就是解动态规划时需要开一个数组,数组的每个元素f[i]或者f[i][j]代表什么,类似解数学题中,xyz代表什么一样,具体分为下面两个步骤:
    • 研究最优策略的最后一步
    • 化为子问题。
  1. 转移方程
  • 根据子问题定义直接得到;
  1. 初始条件和边界情况
  • 初始条件一般都是f[0]f[1]这种,初始条件是无法根据转移方程确定的点
  • 边界条件主要是看数组的边界,数组越不越界。
  1. 计算顺序
  • 利用之前的计算结果

3. 动态规划实例

3.1 硬币问题

3.1.1 问题

题目:有三种硬币,面值分别为2、5、7,每种硬币有无限个,买一本书需要27元,如何用最少的硬币整好付清。

3.1.2 问题分析

根据2中解题步骤来分析。这是一个求最大最小值问题,考虑用动态规划来求解。

  1. 确定状态
  • 最后一步。
    假设已经有最优策略,即有k枚硬币面值分别为 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak,其面值和为27,即 a 1 + a 2 + . . . , + a k = 27 a_1+a_2+...,+a_k=27 a1+a2+...,+ak=27。所以一定存在最后的硬币 a k a_k ak,除去这枚硬币,前面所有的硬币面值加起来是 27 − a k 27-a_k 27ak,即 a 1 + a 2 + . . . , + a k − 1 = 27 − a k a_1+a_2+...,+a_{k-1}=27-a_k a1+a2+...,+ak1=27ak,如下图所示。
    又因为是最优策略,所以拼出的 27 − a k 27-a_k 27ak的硬币数也是最少的,否则这就不是最优策略了。
    在这里插入图片描述
  • 所以得到子问题:最少用多少枚硬币可以拼出总面值为 27 − a k 27-a_k 27ak的序列。注:原问题是:最少用多少枚硬币可以拼出总面值为27的序列。所以我们可以提取标黄部分为状态,即设状态 f [ x ] = f[x]= f[x]=最少用多少枚硬币拼出 x x x,我们的总目标是求 f [ 27 ] f[27] f[27]
  • 子问题:最后一枚硬币 a k a_k ak面值只可能为2、5、7,如果 a k = 2 a_k=2 ak=2,则 f [ 27 ] = f [ 27 − 2 ] + 1 f[27]=f[27-2]+1 f[27]=f[272]+1;如果 a k = 5 a_k=5 ak=5,则 f [ 27 ] = f [ 27 − 5 ] + 1 f[27]=f[27-5]+1 f[27]=f[275]+1;如果 a k = 2 a_k=2 ak=2,则 f [ 27 ] = f [ 27 − 7 ] + 1 f[27]=f[27-7]+1 f[27]=f[277]+1;所以, f [ 27 ] = m i n { f [ 27 − 2 ] + 1 , f [ 27 − 5 ] + 1 , f [ 27 − 7 ] + 1 } f[27]=min\{f[27-2]+1,f[27-5]+1,f[27-7]+1\} f[27]=min{f[272]+1,f[275]+1,f[277]+1}
  1. 转移方程
    在这里插入图片描述
  2. 初始条件和边界条件
    总目标 f [ x ] = m i n { f [ x − 2 ] + 1 , f [ x − 5 ] + 1 , f [ x − 7 ] + 1 } f[x]=min\{f[x-2]+1,f[x-5]+1,f[x-7]+1\} f[x]=min{f[x2]+1,f[x5]+1,f[x7]+1}
  • 边界条件::x-2,x-5,x-7小于0怎么办,小于0的时候必须得停下来,因为不存在小于0的面值,所以令 f [ k ] = + ∞ , k < 0 f[k]=+∞,k<0 f[k]=+,k<0,比如 f [ 1 ] = m i n { f [ − 1 ] + 1 , f [ − 4 ] + 1 , f [ − 6 ] + 1 } = + ∞ f[1]=min\{f[-1]+1,f[-4]+1,f[-6]+1\}=+∞ f[1]=min{f[1]+1,f[4]+1,f[6]+1}=+
  • 初始条件: f [ 0 ] = 0 f[0]=0 f[0]=0,f[0]没法根据上面的式子推导出来,但我们可以根据题目推出只要0个硬币就可以拼出总面值为0的序列。
  1. 计算顺序
    本题是正序的,因为当我们要计算f[X]时,f[X-2],f[X-5],f[X-7]必须都已知。
    在这里插入图片描述在这里插入图片描述

3.1.3 代码

public class coinDynamic {

    // A存储各种面值的硬币,M是目标面值之和
    public static int coinChange(int[] A ,int M){
        int[] f = new int[M+1];
        // 初始条件
        f[0] = 0;

        for (int i = 0; i < M; i++) {
            // 为了比较最小值,必须先初始化设置一个很大的数
            f[i] = Integer.MAX_VALUE;

            // 遍历所有面值的硬币
            for(int j = 0;j < A.length;j++){

                // 如果满足边界条件,并且不能越界,
                // 如果f[i-A[j]]==Integer.MAX_VALUE,那么Integer.MAX_VALUE+1就溢出了
                if (i >= A[j] && f[i-A[j]]<Integer.MAX_VALUE){
                    f[i] = Math.min(f[i],f[i-A[j]]+1);
                }
            }
        }
        // 若所给硬币无法组成总的面值,返回-1
        if (f[M]==Integer.MAX_VALUE)
            return -1;
        return f[M];
    }
}

3.2 机器人路径问题

3.2.1 问题

如下图所示,给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一步只可以向下或者向右走一步,问有多少种不同的方式走到右下角。
在这里插入图片描述

3.2.2 问题分析

这是动态规划中的计数问题。

  1. 确定状态
  • 最后一步:聚焦机器人最后挪动的一歩,右下角坐标为(m-1,n-1),那么前一步机器人一定在(m-2,n-1)或者(m-1,n-2),如下图所示。
    在这里插入图片描述
  • 子问题:由加法原理可知,机器人走到(m-1,n-1)的方式数等于机器人走到(m-2,n-1)加上机器人走到(m-1,n-2)的方式数。原问题为有多少种方式从左上角走到(m-1,n-1),子问题是有多少种方式从左上角走到(m-2,n-1)和(m-1,n-2)。
  1. 转移方程
  • 根据1中的子问题可以得出转移方程为:
    在这里插入图片描述
  1. 边界条件与初始条件
  • 边界条件:由于机器人只能向下或向右移动,所以i=0或者j=0时,只能从一个方向过来,所以 f [ i ] [ j ] = 1 f[i][j]=1 f[i][j]=1
    在这里插入图片描述

  • 初始条件:在右上角[0,0]处,机器人只有一种方式到达,即 f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1.

  1. 计算顺序
    可以按照行或者列计算,这里以行为例,先计算到达第1行每个格子的方式数,再计算第2行,以此类推,计算到最后一行时,取最后一个数就是答案,如下图所示。
    在这里插入图片描述

3.2.3 代码实现

public static int uniquePaths(int m, int n) {
        int[][] f = new int[m][n];

        for (int i = 0; i < m; i++) {   // 按行计算
            for (int j = 0; j < n; j++) {  // 扫描第i行中所有列
                // 边界条件与初始条件
                if (i==0 || j==0){
                    f[i][j] = 0;
                }else {
                    f[i][j] = f[i-1][j] + f[i][j-1];
                }
            }
        }
        // 最后一个就是答案
        return f[m-1][n-1];

3.3 青蛙跳石头问题

3.3.1 题目

有n块石头分别放在x轴的0,1,…,n-1位置,一只青蛙在石头0,想要跳到石头n-1,若青蛙在第i块石头上,他最多可以向右跳距离ai,问青蛙能否跳到石头n-1。

在这里插入图片描述
为什么说输入1的输入是True呢?
初始时刻青蛙在a[0]位置,可以最多向右跳2个位置,可以跳到a[2]的位置;同理,又因为a[2]=1,此时青蛙可以跳到a[3]的位置;a[3]=1,所以可以跳到a[4]=4的位置。
输入2的输出就是False,青蛙无论怎么跳都跳不到a[4]=4的位置,究其原因,是因为没有一块石头能够达到a[4]=4的位置。

3.3.2 题目分析

这是一个存在型动态规划问题。

  1. 确定状态
  • 最后一步:青蛙从第i块石头跳到第n-1块(最后一块)石头上,i<(n-1),假设这两块石头之间距离为 a i a_i ai,所以必须满足 d i s t ( i , n − 1 ) < = a i dist(i,n-1)<=a_i dist(i,n1)<=ai
    在这里插入图片描述
  • 化为子问题:青蛙能够从石头i跳到石头n-1(最后一块)上,青蛙能够跳到石头i,……
  1. 转移方程
    在这里插入图片描述
  2. 边界条件与初始条件
  • 边界条件:枚举的i和j都不会越界,所以没有边界条件

  • 初始条件:f[0]=true,因为青蛙一开始就在石头0上

  1. 计算顺序
    从头开始计算,不能倒着计算,倒着也不方便算。
    在这里插入图片描述

3.3.3 代码分析

public class canJump {
    public static boolean canJump(int[] A) {
        int size = A.length;
        boolean[] f = new boolean[size];
        f[0] = true;

        for (int j = 1; j < size; j++) {
            f[j] = false;   // 默认应该是跳不过去
            for (int i = 0; i < size; i++) {
                f[j] = f[i] && (i + A[i] >= j);
            }
        }
        return f[size-1];
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值