一次性理清动态规划---例题图解

一、什么是动态规划?

给定一个矩形网络,一个机器人从左上角出发,每次可以向下或向右走一步
在这里插入图片描述
题目A:求有多少种方式走到右下角(√ 可用动态规划求解)
题目B:输出所有走到右下角的路径(× 递归)

动态规划题目特点

1.计数

  • 有多少种方式走到右下角
  • 在 n 个数中,有多少种方法选出 k 个数使得和为 sum

2.求最值

  • 从左上角走到右下角路径的最大数字和
  • 给定一个序列,求最长上升子序列长度

3.存在性

  • 取石子游戏,先手是否必胜
  • 能不能选出 k 个数使得和为 sum

二、常规解题步骤

1. 确定状态

状态在动态规划中的作用属于“定海神针”。

状态,简单来说就是在解动态规划题目时需要一个数组,数组的每个元素 f[i] 或者 f[i][j] 代表什么

  • 类似于数学中 x,y,z 代表什么
  • 确定状态的两个意识:
    1. 最后一步:最优策略中的最后一个决策
    2. 子问题:与原问题一样,只是规模变小了

2. 转移方程

将函数 f(X) 转换为数组 f[X]

3. 初始条件和边界情况

边界值:保证数组不要越界。如果不能拼出Y,就定义f[Y]=正无穷

例如:f[-1] = f[-2] = … = 正无穷

初始条件:由转移方程算不出来,需要手动定义。

4. 计算顺序

利用之前的计算结果

大多数动态规划的题目都是从小到大的计算顺序,f[0],f[1],f[2],f[3]……
大多数二维数组的计算顺序:从上到下,从左到右

三、例题图解

1. 零钱兑换问题

求最值的动态规划问题
在这里插入图片描述
思路

a. 确定状态

最后一步:虽然我们不知道最优策略是什么,但是最优策略肯定是 k 枚硬币面值加起来是27------所以一定有一枚最后的硬币ak,除掉这枚硬币之后,其余硬币面值加起来是27-ak
在这里插入图片描述

关键点一:我们不关心前面的 k-1 枚硬币是怎么拼出27-ak的(可能有一种拼法,可能有很多种),而且我们甚至都还不知道 k 和ak,但是我们确定前面的硬币拼出了 27-ak
关键点二:因为是最优策略,所以拼出来的27-ak的硬币个数一定要是最少,否则就不是最优策略了

子问题
原来我们要求最少多少枚硬币拼出27,现在我们要求最少多少枚硬币拼出 27-ak 。

问题的规模减小了,也就是确定了子问题,子问题出来便可以确定状态

为了简化定义,我们设状态 f(X)=最少用多少枚硬币拼出X
原问题是f(27)现在变成求 f(27-ak)

因为最后一枚硬币只能是2,5,7,所以:
如果ak=2,f(27)=f(27-2)+1(加上最后一枚硬币2)
如果ak=5,f(27)=f(27-5)+1(加上最后一枚硬币5)
如果ak=7,f(27)=f(27-7)+1(加上最后一枚硬币7)
在这里插入图片描述

b. 转移方程

设状态 f[x]=最少多少枚硬币拼出x
在这里插入图片描述

c. 初始条件和边界情况

f[X]=min{f[X-2]+1,f[X-5]+1,f[X-7]+1}

问题:X-2,X-5,X-7小于0怎么办?怎么停下来?

f[1] = min{f[-1]+1,f[-4]+1,f[-6]+1} = 正无穷,表示拼不出来1

初始条件:f[0]=0

因为若由转移方程计算得出 f[0]=正无穷,但是已知 f[0]=0

d. 计算顺序

初始条件:f[0]=0
然后计算:f[1],f[2]……f[27]

当我们计算f[X]时,f[X-2],f[X-5],f[X-7]都已经得到结果了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
每一种尝试3种硬币,一共27步。
与递归算法相比,没有重复运算。
时间复杂度(需要进行的步数):27*3

e. 实现代码
public class Solution{
    //A:{2,5,7}  M:27
    public int coinChange(int[] A, int M) {
        int[] f = new int[M+1]; //0……n:[n+1]
        int n = A.length;
        f[0] = 0;  //初始化

        //拼出 f[1],f[2],……,f[27] f[i]
        for (int i = 1;i <= M ;i++){
            f[i] = Integer.MAX_VALUE;//初始化为无穷大,假设拼不出来
            //最后一枚硬币 A[j]
            for (int j = 0;j < n;j++){
                //需要拼的数 i 一定大于 A[j]; 无穷大+1 越界
                if (i >= A[j] && f[i-A[j]] != Integer.MAX_VALUE){
                    f[i] = Math.min(f[i-A[j]]+1,f[i]);
                }
            }
        }
        if(f[M] == Integer.MAX_VALUE){
            f[M] = -1; //若拼不出来27,返回-1
        }
        return f[M];
    }
}

2. 唯一路径问题

计数型动态规划问题
在这里插入图片描述

a. 确定状态

最后一步:无论机器人用何种方式到达右下角,总有最后挪动的一步-----向右或向下

右下角坐标设为(m-1,n-1),那么前一步机器人一定是在(m-2,n-1)或(m-1,n-2)处

子问题
那么,假设有X种方式走到(m-2,n-1),有Y种方式走到(m-1,n-2),则最后有X+Y种方式走到(m-1,n-1)

即,子问题是机器人有多少种方式从左上角走到(m-2,n-1)和(m-1,n-2)

b. 转移方程

在这里插入图片描述

c. 初始条件和边界情况

初始条件:f[0][0] = 1,因为机器人只有一种方式到左上角

边界情况:i = 0 或 j = 0,则前一步只能有一个方向过来–>f[i][j] = 1
在这里插入图片描述

d. 计算顺序
  • f[0][0] = 1
  • 计算第0行:f[0][0],f[0][1],……,f[0][n-1]
  • 计算第1行:f[1][0],f[1][1],……,f[1][n-1]
  • ……
  • 计算第m-1行:f[m-1][0],f[m-1][1],……,f[m-1][n-1]
  • 答案是 f[m-1][n-1]
  • 时间复杂度(计算步数):O(MN)
  • 空间复杂度(数组大小):O(MN)
e. 代码实现
public class Solution{
    public 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++){ //列:从左到右
                if(i == 0 || j == 0){
                    f[i][j] = 1;  //初始化第一行和第一列
                }else{
                    f[i][j] = f[i-1][j] + f[i][j-1];
                }
            }
        }
        return f[m-1][n-1];
    }
}

3. 跳跃游戏问题

存在型动态规划问题
在这里插入图片描述

a. 确定状态

最后一步:如果青蛙能跳到最后一块石头n-1,我们考虑它跳的最后一步。

  • 这一步是从石头 i 跳过来,i < n-1
  • 这时主要满足两个条件:
    1. 青蛙可以跳到石头 i
    2. 最后一步不超过跳跃的最大距离:n-1-i <= ai
      在这里插入图片描述

子问题
那么,我们需要知道青蛙能不能跳到石头 i(i < n-1),而我们原来要求青蛙能不能跳到石头 n-1

状态:设 f[j] 表示青蛙能不能跳到石头 j

b. 转移方程在这里插入图片描述

OR:for循环时只要有一个 i 满足条件(能跳到 j)就是true

c. 初始条件和边界情况

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

边界情况:没有越界

d. 计算顺序

由转移方程决定,从小到大算,答案为f[n-1]

  • 时间复杂度:O(N²)
  • 空间复杂度(数组大小):O(N)
e. 代码实现
public class Solution{
    public boolean canJump(int[] A) {
        int n = A.length;
        boolean[] f = new boolean[n];
        f[0] = true;

        for (int j = 1;j < n;j++){
            f[j] = false; //假设跳不到
            for (int i = 0; i < j; i++) { //枚举之前的石头 上一跳:i-->j
                if (f[i] && i + A[i] >= j){
                    f[j] = true;
                    break;
                }
            }
        }
        return f[n-1];
    }
}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值