0
点赞
收藏
分享

微信扫一扫

Python进阶(1) | 使用VScode写单元测试

首先看动态规划的三要素:重叠子问题、最优子结构和状态转移方程。

重叠子问题:存在大量的重复计算

最优子结构:

状态转移方程:当前状态转移成以前的状态

动态规划的解题步骤主要有:

  • 确定 dp 数组以及下标的含义
  • 状态转移方程、递推公式
  • dp数组初始化、遍历顺序
  • 写代码验证

直接看实际的算法题

1.LeetCode70. 爬楼梯

实际上就是斐波那契算法,我们按最后一次爬楼梯的情形:只有爬1个或者2个台阶,如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以状态转移方程就是 f(n) = f(n-1) + f(n-2)

public int climbStairs(int n) {
    if(n == 1) {
        return 1;
    }
    int[] dp = new int[n + 1];
    dp[0] = 1;
    dp[1] = 1;
    dp[2] = 2;
    for(int i = 3; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

变体:如果可以爬1、2、3、4…m 级台阶,如何求最后的次数?根据上面的图可以得到状态方程:

f(n) = f(n-1) + f(n-2) +...+f(n - m)所以解题代码为:

public int climbStairs2(int n) {
    int[] dp = new int[n+1];
    dp[0] = 1;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; i <= m; j++) {
            if(i - j >= 0) {
                dp[i] = dp[i - j];
            }
        }
    }
    return dp[n];
}
2.LeetCode746. 使用最小花费爬楼梯

这题的思路和上一题爬楼梯很像,我们还是自顶向下来考虑,

如果最后一次选择记为f(n) 那么需要考虑f(n-1)f(n-2)谁更小,然后再加上当前的花费值。那么状态转移方程为:f(n) = min(f(n-1), f(n-2)) + cost[n]。因此代码就容易了:

public int minCostClimbingStairs(int[] cost) {
    int[] dp = new int[cost.length];
    dp[0] = cost[0];
    dp[1] = cost[1];
    for(int i = 2; i < cost.length; i++) {
        dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];
    }
    //因为可以一次性爬两层,所以最后再比较倒数一次和倒数第二次的爬楼梯花费
    return Math.min(dp[cost.length - 1], dp[cost.length - 2]);
}
3.LeetCode62. 不同路径

看题目可以知道,和前两题不一样,需要我们从两个维度去考虑。因此可以选择二维dp数组来解决问题,按照动态规划解题步骤:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 确定dp数组的含义
    • dp[m][n] 表示从 [0][0][m][n] 的路径条数,数组内的下标表示所处的位置
  2. 确定状态转移方程
    • 我们知道当前位置状态来源于左侧和上侧位置状态因此可以写成:
    • dp[m][n] = dp[m-1][n] +dp[m][n-1]
  3. 确定dp数组初始状态和遍历顺序
    • 初始状态要注意,和一维dp数组考虑一个初始值不同。我们要考虑二维多个初始值:
      • dp[0][i]dp[j][0] 这两列上,路径条数都应该为1
    • 遍历顺序,按照从小到大的原则进行

综合上面的思路,可以写出代码

public int uniquePaths(int m, int n) {
    int[][] dp = new int[m][n];
    //初始化dp数组
    for(int i = 0; i < m; i++) dp[i][0] = 1;
    for(int j = 0; j < n; j++) dp[0][j] = 1;
    //遍历顺序
    for(int i = 1; i < m; i++) {
        for(int j = 1; j < n; j++) {
            //状态转移方程
            dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
        }
    }
    return dp[m - 1][n - 1];
}
4.LeetCode63. 不同路径 II

这题和上一题的区别就是有了障碍,那么我们该如何考虑这个障碍呢?主要分成两个方面:

  • 初始化时遇到障碍:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 顺序遍历时遇到障碍:

    也就是题目给出的情况,这种情况将障碍处设置为0 即可

其余的和上一题类似,所以直接给出代码:

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
    // 数组的行数
    int m = obstacleGrid.length;
    // 数组的列数
    int n = obstacleGrid[0].length;
    int[][] dp = new int[m][n];
    //障碍及后面方格统一初始化
    for(int i = 0; i < m; i++) {
        if(obstacleGrid[i][0] == 1) {
            break;
        }
        dp[i][0] = 1;
    }
    for(int j = 0; j < n; j++) {
        if(obstacleGrid[0][j] == 1) {
            break;
        }
        dp[0][j] = 1;
    }
    for(int i = 1; i < m; i++) {
        for(int j = 1; j < n; j++) {
            //遍历过程遇见障碍,直接跳过
            if(obstacleGrid[i][j] == 1) {
                dp[i][j] = 0;
                continue;
            }
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        }
    }
    return dp[m - 1][n - 1];
}
5.leetCode343. 整数拆分

我们按照动态规划解题步骤一步步来:

  • 1.确定dp数组的含义以及下标:

    • dp数组表示的是最大乘积,下标表示整数n
  • 2.确定状态转移方程:

    • 考虑到一个整数至少要分成2个及以上的整数,所以:

      • 分成2个可以表达成 i * j
      • 分成2个以上可以表达成 j * dp[i - j]
    • 所以转移方程应该为: dp[i] = max(j * dp[i - j], j * i)

      • 但是这里我们会发现得不到符合题意的值:

        外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

        因为每次取的都是最后一个,比如 dp[10] = max(9 * dp[1], 9 * 1) ,所以每次需要与前面的 dp[i] 比较,得到最后的最大值。

      • 因此转移方程应为:dp[i] = max(dp[i],max(j * dp[i - j], j * (i - j)))

  • 3.确定初始值,遍历顺序

    • dp[2] = 1,dp[1] = 1,dp[0] = 1 (dp[0] 和 dp[1] 实际上不符合题意,虽然赋值1 不影响结果)
    • i从3到n 遍历,j 从 1 到 i - 2 遍历
  • 4.根据上面三个步骤写代码:

public int integerBreak(int n) {
    int[] dp = new int[n + 1];
    //初始化赋值
    dp[2] = 1;
    for(int i = 3; i <= n; i++) {
        //从下标为2开始遍历
        for(int j = 1; j < i - 1; j++) {
            dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
        }
    }
    return dp[n];
}
6.LeetCode96. 不同的二叉搜索树

这题确实不好解决,二叉搜索树的种树这个变量不好确定。需要以左右子树为基础进行考虑,下面引用代码随想录的思路:

所以状态转移方程为 dp[i] += dp[j] * dp[i - j - 1];

初始值dp[0] = 1;dp[1] = 1,按照节点个数进行遍历。直接下出如下代码

public int numTrees(int n) {
    int[] dp = new int[n + 1];
    //初始化赋值
    dp[0] = 1;
    dp[1] = 1;
    for(int i = 2; i <= n; i++) {
        for(int j = 0; j < i; j++) {
            dp[i] += dp[j] * dp[i - j - 1];
        }
    }
    return dp[n];
}
举报

相关推荐

0 条评论