动态规划第二弹 基础提升

简介: 动态规划第二弹 基础提升

前言

在上一篇文章 动态规划从入门到LeetCode - 掘金 (juejin.cn) 中, 我们学习了怎么去做动态规划类的题目, 并进行了简单题目的练习

今日任务:

动态规划解题五部曲

  • 确定 dp 数组以及下标的含义
  • 确定递推公式
  • dp 数组如何初始化
  • 确认遍历顺序
  • 举例推导 dp 数组

62. 不同路径

题目描述

一个机器人位于一个 m x n **网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:

网络异常,图片无法展示
|

输入: m = 3, n = 7
输出: 28
复制代码

示例 2:

输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
复制代码

示例 3:

输入: m = 7, n = 3
输出: 28
复制代码

示例 4:

输入: m = 3, n = 3
输出: 6
复制代码

 

提示:

  • 1 <= m, n <= 100
  • 题目数据保证答案小于等于 2 * 109

思路分析

  1. 确定 dp 数组以及下标的含义:
    本题 机器人 处于 网格 中, 他有 两个 前进的方向, 那么本题就和上一篇文章做的 爬楼梯等题不一样了, dp 数组应该是 ==> dp[i][j] i代表 x轴 方向前进, j 代表 y轴 方向前进, dp[i][j] 代表到达该点的路径条数
  2. 确定递推公式
    由于本题有两种方向可选, 且到达的点位是固定可知的, 所以 dp[i][j] = dp[i-1][j] + dp[i][j-1]
  3. dp数组 如何初始化
  • 因为只能向下或者向右前进
  • 所以 dp[i][0] = 1;
  • 同理 dp[0][j] = 1;
  1. 确认遍历顺序
  • 因为只能向下或者向右前进
  • 所以直接从左向右遍历就可以了
  1. 举例推导 dp数组
  • 当 m = 3, n = 7时, 推导每个坐标值如下所示

网络异常,图片无法展示
|

代码展示

public int uniquePaths(int m, int n) {
    // 确定 dp 数组
    int[][] dp = new int[m][n];
    // 循环遍历数组
    for (int i = 0; i < m; i++) {
        for (int j = 0;j < n; j++) {
            // 初始化 dp 数组
            if (i == 0 || j == 0){
                dp[i][j] = 1;
                continue;
            }
            // 递推公式
            dp[i][j] = dp[i-1][j] + dp[i][j-1];
        }
    }
    // 返回 机器人 抵达右下角有多少种路径
    return dp[m-1][n-1];
}
复制代码

提交结果

网络异常,图片无法展示
|

63. 不同路径 II

题目描述

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 10 来表示。

示例 1:

网络异常,图片无法展示
|

输入: obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出: 2
解释: 3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
复制代码

示例 2:

网络异常,图片无法展示
|

输入: obstacleGrid = [[0,1],[0,0]]
输出: 1
复制代码

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j]01

思路分析

首先获取网格 x,y 的长度, 然后创建新数组

  1. 确定dp数组以及下标的含义
    本题和上一题的区别就是多出了 障碍物, 那么 dp数组 的下标及其含义是不变的, 依旧是到达该点位的路径条数
  2. 确定递推公式
    一样的, dp[i][j] = dp[i-1][j] + dp[i][j-1]
  3. dp数组的初始化
  • 只能向下或者向右前进, 但是本题引入了 障碍物的情况, 所以初始化也要有相应的改变
  • 所以 dp[i][0] = obstacleGrid[i][0] == 0 ? 1 : 0;
  • 同理 dp[0][j] = obstacleGrid[i][0] == 0 ? 1 : 0;
  1. 确定遍历顺序
  • 和上一题一样的, 从左到右, 从上到下双层 for 循环进行遍历
  1. 举例推导这边就不举例了

代码展示

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    // 获取长度
    int x = obstacleGrid.length;
    int y = obstacleGrid[x - 1].length;
    // 判断边界, 起点和终点不能有障碍物
    if (obstacleGrid[0][0] == 1 || obstacleGrid[x - 1][y - 1] == 1){
        return 0;
    }
    // 创建 dp 数组
    int[][] dp = new int[x][y];
    // 初始化 dp 数组
    for (int i = 0; i < x && obstacleGrid[i][0] == 0; i++) {
        dp[i][0] = 1;
    }
    for (int i = 0; i < y && obstacleGrid[0][i] == 0; i++) {
        dp[0][i] = 1;
    }
    // 推导下标值
    for (int i = 1; i < x; i++) {
        for (int j = 1; j < y; j++) {
            // 如果有障碍物, 则赋予这个坐标为 0
            dp[i][j] = obstacleGrid[i][j] == 0 ? dp[i-1][j] + dp[i][j-1] : 0;
        }
    }
    return dp[x - 1][y - 1];
}
复制代码

提交结果

网络异常,图片无法展示
|

错误分析

第一次错误

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int length1 = obstacleGrid.length;
        int length = obstacleGrid[length1 - 1].length;
        for (int i = 0; i < length1; i++) {
            for (int j = 0; j < obstacleGrid[i].length; j++) {
                if (i == 0){
                    // 如果是障碍物就给他 0
                    if (obstacleGrid[i][j] == 1){
                        obstacleGrid[i][j] = 0;
                        break;
                    }
                    obstacleGrid[i][j] = 1;
                    continue;
                }else if (j == 0){
                    obstacleGrid[i][j] = obstacleGrid[i][j] == 0 ? 1 : 0;
                    continue;
                }
                obstacleGrid[i][j] = obstacleGrid[i][j] == 0 ? obstacleGrid[i-1][j] + obstacleGrid[i][j-1] : 0;
            }
        }
        return obstacleGrid[length1 - 1][length - 1];
    }
复制代码

网络异常,图片无法展示
|

入口放障碍也是没想到的, 下面是评论...

网络异常,图片无法展示
|

鉴于上述结果, 我在代码中新增了边界控制判断, 如果起点或者终点有障碍, 则直接返回 0, 新增判断代码如下, 然后就出现了第二次错误

if (obstacleGrid[0][0] == 1 || obstacleGrid[length1 - 1][length - 1] == 1){
    return 0;
}
复制代码

第二次错误

这是真的bug, 待我改一改

不得不说, 这个代码写的跟**屎山**一样, 多搞几个 for循环, 改一手吧, 不偷懒, 而是**新建一个 dp 数组来存储到达某个坐标点的路径数目**, 具体可看 代码提交 部分
复制代码

网络异常,图片无法展示
|

343. 整数拆分

题目描述

给定一个正整数 n ,将其拆分为 k正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积

示例 1:

输入: n = 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。
复制代码

示例 2:

输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
复制代码

提示:

  • 2 <= n <= 58

思路分析

  1. 确定 dp数组 以及下标的含义
    dp[i]: 第 i 个数的最大值
  2. 确定递推公式从 i 开始遍历, 遍历到 <= n, 然后 j 再从 1 开始遍历, 遍历到  j <= i-j, 也就是 j 最大值是 i-1, 有两种情况能够获取到 dp[i]
  • dp[i] = j*(i-j);    计算两个数相乘
  • dp[i] = j*dp[i-j];  计算两个及以上的数相乘 (dp[i-j] 是第 i-j 个数的最大值)
  1. dp数组的初始化
    默认 dp[2] = 1, dp[0] 和 dp[1]不用管, 因为 2<=n
  2. 确认遍历顺序
    从左往右开始遍历, i 从 3 开始, j 从 1开始遍历
for (int i = 3; i <= n; i++) {
    for (int j = 1; j <= i-j; j++) {
        // 递推公式
        dp[i] = Math.max(dp[i], Math.max((i-j)*j, dp[i-j]*j));
    }
}
复制代码
  1. 举例推导 pd数组

代码展示

public int integerBreak(int n) {
    // 创建 dp数组
    int[] dp = new int[n + 1];
    // 初始化 dp数组
    dp[2] = 1;
    for (int i = 3; i <= n; i++) {
        for (int j = 1; j <= i-j; j++) {
            // 递推公式
            dp[i] = Math.max(dp[i], Math.max((i-j)*j, dp[i-j]*j));
        }
    }
    return dp[n];
}
复制代码

提交结果

网络异常,图片无法展示
|

总结

经过这三道题的练习, 我对动态规划有了更深一步的理解, 真的就是循序渐进的学习, 奥利给



目录
相关文章
|
4月前
|
C++
【洛谷 P1044】[NOIP2003 普及组] 栈 题解(递归+记忆化搜索)
**NOIP2003普及组栈问题**:给定操作数序列1到n,仅允许push(进栈)和pop(出栈)操作。目标是计算所有可能的输出序列总数。输入包含一个整数n(1≤n≤18)。示例输入3,输出5。当队列空时返回1,栈空则只能入栈,栈非空时可入栈或出栈。AC C++代码利用记忆化搜索求解。
41 1
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(2)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
4月前
|
算法 搜索推荐 测试技术
力扣经典150题解析之二十九:三数之和
力扣经典150题解析之二十九:三数之和
28 0
|
4月前
|
存储 算法
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(1)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(4)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(3)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
4月前
|
算法 容器
【经典LeetCode算法题目专栏分类】【第1期】左右双指针系列:盛最多水的容器、接雨水、回文子串、三数之和
【经典LeetCode算法题目专栏分类】【第1期】左右双指针系列:盛最多水的容器、接雨水、回文子串、三数之和
|
4月前
|
存储
初阶编程题积累(3)——最接近的三数之和(题目描述、示例、题目思路、题解、解析)
初阶编程题积累(3)——最接近的三数之和(题目描述、示例、题目思路、题解、解析)
29 0
|
5月前
|
测试技术
【一刷《剑指Offer》】面试题 9:斐波那契数列(扩展:青蛙跳台阶、矩阵覆盖)
【一刷《剑指Offer》】面试题 9:斐波那契数列(扩展:青蛙跳台阶、矩阵覆盖)
|
12月前
蓝桥杯必备动态规划第二弹-路径问题进阶
蓝桥杯必备动态规划第二弹-路径问题进阶