动态规划-基础篇-02

63.不同路径 II

关键词:动态规划、二维dp数组、边界处理

这题和上一题的基本思路一样,只是多了障碍物,我们只需要在上一题的基础上增加对障碍物的处理即可。首先障碍物会影响我们对最上面和最左边的初始化,我们一旦遇到障碍物,就停止为后面的位置赋值为1,因为唯一的路径都已经被障碍物堵住了。还有在动态规划的过程中对障碍物进行处理,遇到障碍物则将该处的路径值设置为0,其余内容与上一题一样。

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];
        int i=0;
        
        //遇到障碍物则后面的值都为0了
        while(i<n && obstacleGrid[0][i]==0){
            dp[0][i] = 1;
            i++;
        }
        i=0;
        while(i<m && obstacleGrid[i][0]==0){
            dp[i][0] = 1;
            i++;
        }
        
        for(i=1;i<m;i++){
            for(int j=1;j<n;j++){
                if(obstacleGrid[i][j] == 0) dp[i][j] = dp[i][j-1]+dp[i-1][j];
                else dp[i][j] = 0;
            }
        }

         return dp[m-1][n-1]; 
    }
}

我的解答(思路和解答一致,需要特殊考虑的地方:初始化的边界存在障碍物,终点是障碍物):

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int w=obstacleGrid.length, h=obstacleGrid[0].length;
        int[][] dp = new int[w][h];
        boolean flag = false;
        for(int i=0;i<w;++i){
            if(flag){
                dp[i][0]=0;
                continue;
            }
            if(obstacleGrid[i][0]==1) flag = true;
            dp[i][0]=1-obstacleGrid[i][0];  
        } 
        flag = false;
        for(int j=0;j<h;++j){
            if(flag){
                dp[0][j]=0;
                continue;
            }
            if(obstacleGrid[0][j]==1) flag = true;
            dp[0][j]=1-obstacleGrid[0][j]; 
        }
        for(int i=1;i<w;++i){
            for(int j=1;j<h;++j){
                if(obstacleGrid[i][j]==1){
                    dp[i][j] = 0;
                }else{
                    dp[i][j] = (1-obstacleGrid[i][j-1])*dp[i][j-1] + (1-obstacleGrid[i-1][j])*dp[i-1][j];
                }
            }
        }
        return dp[w-1][h-1];
    }
}

343.整数拆分

关键词:动态规划、边界处理、数学

动态规划的dp数组记录拆分的乘积的最大值,我们可以遍历n的每个拆分,每次拆分都是拆成两个数。我们可以这样思考,例如3,可以拆分为1和2,也可以拆分为1和对2进行进行拆分,这样我们就推导出递推公式为temp = Math.max(j*(i-j),j*dp[i-j]);。由于这种拆分是遍历的,并从中选取最大值,因此dp[i]会比较递归中产生的值,选取其中最大的。

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n+1];
        for(int i=2;i<n+1;i++){
            for(int j=0;j<i;j++){
                int temp = Math.max(j*(i-j),j*dp[i-j]);
                dp[i] = Math.max(temp,dp[i]);
            }
        }

        return dp[n];
    }
}

我的解答:

动态规划五部曲
  1. 确定dp数组及下标的含义。dp[i]表示正整数 i 拆分后可以获得的最大乘积。
  2. 确定递推公式。一开始想到dp[i]=max(1*dp[i-1],2*dp[i-2];3*dp[i-3]…(i-2)*dp[2]),没有dp[1]。但测试用例报错后发现上述划分方式默认了至少划分3个数(因为dp[j]又至少划分为2个数),没考虑只划分2个数的情况,后来需要比较划分2个数和划分大于2个数哪种乘积更大:Math.max(j*dp[i-j],j*(i-j))
  3. 初始化dp数组。dp[2]=1*1 ; dp[3]=1*2=2 ; dp[4]=2*2=4 ; dp[5]=2*3=6
  4. 确定遍历顺序。
  5. 举例推导dp数组。
class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n+1];
        dp[2]=1;
        if(n==2) return 1;
        for(int i=3;i<=n;i++){
            int max = 0;
            for(int j=1;j<i-1;j++){
                int temp = Math.max(j*dp[i-j],j*(i-j));
                if(temp>max) max = temp;
            }
            dp[i] = max;
        }
        return dp[n];
    }
}

96.不同的二叉搜索树

关键词:动态规划、数学、二叉树

我们可以这样分解问题,将二叉树分为两边,左边可能出现的二叉树种类*右边可能出现二叉树的种类相乘可以得到当前这棵二叉树可能的种类。以3个节点的情况为例子,因为要留一个节点作为根结点,左右总共可分配的节点数为2,我们可以左0右2、左1右1、左2右0三种情况,我们只要将其求和即可得到3个节点时候的不同二叉树的数目。因此我们可以使用dp数组记录每个节点数目下,不同的二叉树数目有多少种,然后遍历即可得到答案。初始化的话就是将节点为0时值为1,节点为1时值也为1。

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n+1];
        int count = 0;
        dp[0] = 1;
        dp[1] = 1;
        
        for(int i=2;i<=n;i++){
            count = 0;
            for(int j=0;j<i;j++){
                count += (dp[j]*dp[i-j-1]);
            }
            dp[i] = count;
        }
        return dp[n];
    }
}
我的实现:

使用dp[3]来找递推规律时,按照数字1为根,数字2为根,数字3为根这三种情况列出,虽然树中的节点的值看起来和dp[1],dp[2]找不到关系,但若不看节点中的数字,抽象到只看子树的节点的个数对应有多少种二叉搜索树,就可以找到数值上的规律。

初始化:dp[0]=1, dp[1]=1, dp[2]=2

dp[3]=dp[0]*dp[2](数字1为根,左边0节点,右边2节点)+dp[1]*dp[1](数字2为根,左边1节点,右边1节点)+dp[2]*dp[0](数字3为根,左边2节点,右边0节点)

class Solution {
    public int numTrees(int n) {
        int[] dp = new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;++i){
            int sum=0;
            for(int j=0;j<i;++j) sum+=dp[j]*dp[i-1-j];
		   dp[i] = sum;
        }
        return dp[n];
    }
}
经验总结:

一般简单的动态规划题中,dp[i]仅与有限个dp[j]{j<i}相关,要么是等式关系,要么是取最小或取最大值关系,这种情况只需要一个循环就能搞定。但而此题与343.整数拆分的情况类似,即dp[i]与每一个dp[j]{j<i,j=0,1,2…i-1}都有关系,这就需要额外使用一个循环来遍历,每一个dp[j]。

posted @ 2025-04-09 17:06  码宝  阅读(43)  评论(0)    收藏  举报