动态规划入门:三道 LeetCode 题目详解

119 阅读4分钟

动态规划是一种常用的算法思想,可以解决一些具有重叠子问题和最优子结构性质的问题。在本文中,我们将介绍三道与动态规划相关的 LeetCode 题目,并提供解题思路和代码实现。

1. 删除并获得点数

题目描述:给定一个整数数组 nums,每次操作中,选择任意一个 nums[i],删除它并获得 nums[i] 的点数。之后,需要删除所有等于 nums[i] - 1 和 nums[i] + 1 的元素。求通过这些操作能获得的最大点数。

解题思路:

  1. 计算数组中每个数字出现的次数 -> count.get(i) 数字i出现的次数
  2. dp[i] 表示 选择到i时的最大点数
  3. 初始化dp[1],dp[1] = count.get(1) ? count.get(1) * 1 : 0;
  4. 对于 i 两个选择 1. 删除 dp[i] = d[i-2] + i * num[i] 2. 不删除 dp[i] = dp[i-1]
  5. 遍历 2 ~ maxNum 的数字,分别计算两种选择的路径数,取较大值
var deleteAndEarn = function (nums) {
    if (nums.length === 0) return 0; // 空数组返回0
    let count = new Map();
    let maxNum = 0;
    for (let num of nums) {
        count.set(num, (count.get(num) || 0) + 1); // 计算每个数字出现的次数
        maxNum = Math.max(maxNum, num); // 计算数组中最大的数字
    }
    let dp = new Array(maxNum + 1).fill(0);
    dp[1] = count.get(1) ? count.get(1) * 1 : 0; // 初始化 dp[1]
    for (let i = 2; i <= maxNum; i++) {
        dp[i] = Math.max(dp[i - 2] + (count.get(i) || 0) * i, dp[i - 1]);
    }
    return dp[maxNum];
};

2. 不同路径

题目描述:一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。问总共有多少条不同的路径能够到达网格的右下角。

解题思路:

  1. 初始化 dp 数组,dp[i][j] 表示到达 (i,j) 的路径数
  2. 第一行和第一列的路径数都为 1(机器人只能向下或者向右移动)
  3. dp[i][j] = dp[i-1][j] + dp[i][j-1] 表示到达 (i,j) 的路径数等于到达 (i-1,j) 的路径数 + 到达 (i,j-1) 的路径数
  4. 最后返回 dp[m-1][n-1] 表示到达 (m,n) 的路径数
var uniquePaths = function (m, n) {
    let dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
    // 初始化第一行和第一列的路径数都为 1
    for (let i = 0; i < m; i++) {
        dp[i][0] = 1;
    }
    for (let j = 0; j < n; j++) {
        dp[0][j] = 1;
    }
    // 计算其他位置的路径数
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }
    return dp[m - 1][n - 1];
};

3. 最小路径和

题目描述:给定一个包含非负整数的 m x n 网格,找出一条从左上角到右下角的路径,使得路径上的数字总和最小。每次只能向下或者向右移动一步。

解题思路:

  1. 获取网格的行数和列数
  2. dp[i][j] 表示到达(i,j) 时路径的最小和
  3. 初始化第一列和第一行的路径和
  4. 填充剩余格子 dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
  5. 返回到达(m,n) 时的路径和
var minPathSum = function (grid) {
    let m = grid.length;
    let n = grid[0].length;
    let dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
    dp[0][0] = grid[0][0];
    // 初始化第一列和第一行的路径和
    for (let i = 1; i < m; i++) {
        dp[i][0] = dp[i - 1][0] + grid[i][0];
    }
    for (let j = 1; j < n; j++) {
        dp[0][j] = dp[0][j - 1] + grid[0][j];
    }
    // 计算剩余格子的路径和
    for (let i = 1; i < m; i++) {
        for (let j = 1; j < n; j++) {
            dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
        }
    }
    return dp[m - 1][n - 1];
};

总结:

这三道题目都涉及到动态规划算法,但在具体问题和解题思路上有一些相同点和区别。

相同点:

  1. 动态规划思想:这三道题目都可以使用动态规划来解决,通过将大问题拆解为小问题并保存中间结果,最终得到最优解。
  2. 状态定义:在解题过程中,需要定义合适的状态来表示问题的子结构,并根据子结构之间的关系进行状态转移。

区别:

  1. 问题描述:这三道题目涉及到不同的问题领域。"删除并获得点数"是一个涉及数组操作的问题,"不同路径"是一个二维网格的路径计数问题,而"最小路径和"是一个求解网格路径最小和的问题。
  2. 解题思路:尽管都是动态规划题目,但每道题目的解题思路和状态转移方程有所不同。例如,"删除并获得点数"需要考虑选择删除和不删除的情况,"不同路径"涉及到累加路径数,而"最小路径和"需要选择路径和最小的方向进行移动。
  3. 数据结构:虽然都是使用动态规划,但问题的特点导致在实现过程中可能会使用不同的数据结构,如数组、矩阵等。