代码随想录算法训练营第三十九天| 62. 不同路径 、63. 不同路径 II 、343. 整数拆分 、96. 不同的二叉搜索树

[LeetCode] 62. 不同路径
[LeetCode] 62. 不同路径 文章解释

[LeetCode] 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 * 10^9

[LeetCode] 62. 不同路径 

自己看到题目的第一想法

    1. 没想到递归解法

    2. 因为一个位置只能由它上面和它左面的位置过来,因此 dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

看完代码随想录之后的想法

    不同的初始化方式,让递推公式的实现更优雅。

// 递归解法,o(2^n),  超时
class Solution {
    public int uniquePaths(int m, int n) {
        return dfs(m, n, 0, 0);
    }
    private int dfs(int maxRow, int maxColumn, int row, int column) {
        if (row == maxRow || column == maxColumn) {
            return 0;
        }

        if (row == maxRow - 1 && column == maxColumn - 1) {
            return 1;
        }

        int left = dfs(maxRow, maxColumn, row + 1, column);
        int right = dfs(maxRow, maxColumn, row, column + 1);
        return left + right;
    }
}
// 动态规划解法
class Solution {
    public int uniquePaths(int m, int n) {
        // dp[i][j] 表示从 [0][0] 到达 [i][j] 位置的路径数量
        int[][] dp = new int[m][n];
        // 递推公式:因为一个位置只能由它上面和它左面的位置过来,因此 dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        // 初始化: dp[0][0] = 1, 机器人是站在 [0][0] 位置上的,因此 [0][0] -> [0][0] 可以理解为 1
        dp[0][0] = 1;
        for (int i = 0; i < n; i++) {
            dp[0][i] = 1;
        }
        for (int i = 0; i < m; i++) {
            dp[i][0] = 1;
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]);
            }
        }
        return dp[m - 1][n - 1];
    }
}

 自己实现过程中遇到哪些困难

    无

[LeetCode] 63. 不同路径 II
​​​​​​​[LeetCode] 63. 不同路径 II 文章解释

[LeetCode] 63. 不同路径 II 视频解释​​​​​​

题目:

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

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

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

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

示例 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] 为 0 或 1

[LeetCode] 63. 不同路径 II

自己看到题目的第一想法

    遇到障碍物的位置, dp[i][j] 为0即可

看完代码随想录之后的想法

    多了一个初始化的过程,让循环部分代码更简洁

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int[][] dp = new int[obstacleGrid.length][obstacleGrid[0].length];
        for (int i = 0; i < dp[0].length; i++) {
            if (obstacleGrid[0][i] != 0) {
                break;
            }
            dp[0][i] = 1;
        }
        for (int i = 0; i < dp.length; i++) {
            if (obstacleGrid[i][0] != 0) {
                break;
            }
            dp[i][0] = 1;
        }
        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[i].length; j++) {
                if (obstacleGrid[i][j] != 0) {
                    continue;
                }
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[dp.length - 1][dp[dp.length - 1].length - 1];
    }
}
// 自己最早写的版本,凑合看看吧
class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if (obstacleGrid[0][0] == 1) {
            return 0;
        }
        int[][] dp = new int[obstacleGrid.length + 1][obstacleGrid[0].length + 1];
        dp[1][1] = 1;
        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[i].length; j++) {
                if (obstacleGrid[i - 1][j - 1] != 0) {
                    continue;
                }
                if (i == 1 && j == 1) {
                    // dp[1][1] 不能被重新计算
                    continue;
                }
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
            }
        }
        return dp[dp.length - 1][dp[dp.length - 1].length - 1];
    }
}

自己实现过程中遇到哪些困难

    和路径总和几乎一样,只要抓住关键点, 当[i][j] 位置有障碍时,dp[i][j] = 0 就行。

[LeetCode] 343. 整数拆分
[LeetCode] 343. 整数拆分 文章解释

​​​​​​​​​​​​​​[LeetCode] 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

[LeetCode] 343. 整数拆分 

自己看到题目的第一想法

    真的想不明白,先拆成两个数, 两个数还可以继续拆分成更多的数,怎么模拟我都不懂呀!!!

看完代码随想录之后的想法

    我对动态规划的算法还是没有掌握到精髓, 今天看了更多的题目后, 对于 dp[i] 的定义这句话似乎有了更深刻的理解。我们的一切目的都是为了保证所有的 dp[i] 都符合定义。最终到达结尾的时候,dp[n] 就是我们要求的值。

    同时动态规划的过程, 其实也是遍历了所有优化过的路径, 比暴力递归砍掉了很多不必要的分支, 因此效率高了很多。

class Solution {
    public int integerBreak(int n) {
        // dp[i] 表示数字 i 拆分多个整数后,最大的乘积
        int[] dp = new int[n + 1];
        // 递推公式:dp[i] = Math.max(1 * dp[i - 1], 2 * dp[i - 2])... i/2 * dp[i - i/2])
        // 初始化
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i/2; j++) {
                dp[i] = Math.max(dp[i], j * dp[i - j]);
                // hhh, 神亲的是之前忘记比对 j * (i - j) 这种不拆分的情况, 取 10 模拟 dp 竟然是正确的。。。
                dp[i] = Math.max(dp[i], j * (i - j));
            }
        }
        return dp[n];
    }
}

自己实现过程中遇到哪些困难

    忘记计算比较 j * (i - j) 的情况了。

[LeetCode] 96. 不同的二叉搜索树
[LeetCode] 96. 不同的二叉搜索树 文章解释

[LeetCode] 96. 不同的二叉搜索树 视频解释

题目:

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例 1:

输入:n = 3
输出:5

示例 2:

输入:n = 1
输出:1

提示:

  • 1 <= n <= 19

[LeetCode] 96. 不同的二叉搜索树 

自己看到题目的第一想法

    先拿 n = 4 画一下。

    4 好像是可以分解成 1 + 3, 2 + 2.

    但是就是找不到规律

看完代码随想录之后的想法

    1, 2, 3 ... n - 1, n, 这么多个数字构成搜索二叉树, 那每个节点都要做一次根节点。当一个数字做根节点时,就同时划分出了左子树的数量和右子树的数量。 同时左右子树的节点也都是递增的。 这样就可以不断地划分, 最后化解为 0个节点/1个节点/2个节点 的情况。完成动态规划 

class Solution {
    public int numTrees(int n) {
        // dp[i] 表示用 1 ~ i 个节点组成的二叉搜索树的
        int[] dp = new int[n + 1];
        // 递推公式: dp[i] = dp[0] * dp[i - 1] + dp[1] * dp[i - 2]... + dp[i - 1]*dp[0]
        // 初始化
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
             for (int j = 1; j <= i; j++) {// j 表示当前节点的值, j - 1 表示 j 的左子树的节点数量, i - j 表示右子树节点的数量
                dp[i] += dp[j - 1] * dp[i - j];
             }
        }
        return dp[n];
    }
}

自己实现过程中遇到哪些困难

   看一遍几乎就能自己写出来了, 但是今天看到题目的一霎那还是懵了, 这咋求啊~ 稍微看了一下题解才想起来思路,掌握得比较差吧。

最近更新

  1. TCP协议是安全的吗?

    2024-06-16 02:30:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-16 02:30:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-16 02:30:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-16 02:30:05       18 阅读

热门阅读

  1. 【Git系列】Git LFS常用命令的使用

    2024-06-16 02:30:05       5 阅读
  2. Nginx之HTTP模块详解

    2024-06-16 02:30:05       8 阅读
  3. Mysql的基础命令有哪些?

    2024-06-16 02:30:05       5 阅读
  4. Python笔记 - 运算符重载

    2024-06-16 02:30:05       6 阅读
  5. fastapi相关知识点回顾

    2024-06-16 02:30:05       6 阅读
  6. 力扣-1953

    2024-06-16 02:30:05       7 阅读
  7. 乐观锁和悲观锁

    2024-06-16 02:30:05       6 阅读
  8. Spring框架的原理及应用详解(四)

    2024-06-16 02:30:05       7 阅读