首页 > 其他分享 >leetcode刷题day33|动态规划Part02(62.不同路径、63. 不同路径 II、 343.整数拆分、96.不同的二叉搜索树)

leetcode刷题day33|动态规划Part02(62.不同路径、63. 不同路径 II、 343.整数拆分、96.不同的二叉搜索树)

时间:2024-10-01 13:21:00浏览次数:12  
标签:结点 遍历 int 不同 路径 搜索 为头 dp 96

62.不同路径

机器人从(0 , 0) 位置出发,到(m - 1, n - 1)终点。

动规五部曲

1、确定dp数组(dp table)以及下标的含义
dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。

2、确定递推公式
想要求dp[i][j],只能有两个方向来推导出来,即dp[i - 1][j] 和 dp[i][j - 1]。dp[i][j] = dp[i - 1][j] + dp[i][j - 1],因为dp[i][j]只有这两个方向过来。

3、dp数组的初始化
首先dp[i][0]一定都是1,因为从(0, 0)的位置到(i, 0)的路径只有一条,那么dp[0][j]也同理。

4、确定遍历顺序
dp[i][j]都是从其上方和左方推导而来,那么从左到右一层一层遍历就可以了。这样就可以保证推导dp[i][j]的时候,dp[i - 1][j] 和 dp[i][j - 1]一定是有数值的。

5、举例推导dp数组

代码如下:

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp=new int[m][n];
        for(int i=0;i<m;i++) dp[i][0]=1;
        for(int i=0;i<n;i++) dp[0][i]=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];
    }
}

63. 不同路径 II

思路:这个题目和上一题的区别在于增加了一个障碍物,障碍物位置的可能路径为0。同时注意初始化的时候如果初始化的两边有一个障碍物,那么障碍物后面的格子路径数都为0。

代码如下:

class Solution {
    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 && obstacleGrid[i][0]==0;i++) dp[i][0]=1;
        for(int j=0;j<n && obstacleGrid[0][j]==0;j++) dp[0][j]=1;
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j]=obstacleGrid[i][j]==0?dp[i-1][j]+dp[i][j-1]:0;
            }
        }
        return dp[m-1][n-1];
    }
}

343.整数拆分

思路:求一个数i拆分后乘积的最大值,考虑从让j从1开始遍历计算j*(i-j)和j*dp[i-j],也就是说看分成两部分和分成多个部分哪个更大,保留更大的那个。

动规五部曲

1、确定dp数组(dp table)以及下标的含义
dp[i]:分拆数字i,可以得到的最大乘积为dp[i]。

2、确定递推公式
递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));

在取最大值的时候,还需要比较dp[i]是因为对于每一个j都会得到一个dp[i],最后需要保留最大值。

3、dp的初始化
dp[0] dp[1] ,无法拆分,不进行初始化,从dp[2] = 1开始。

4、确定遍历顺序
递归公式dp[i] 依靠 dp[i - j],所以遍历i一定是从前向后遍历,先有dp[i - j]再有dp[i]。

5、举例推导dp数组

注意:遍历j的时候遍历到i/2即可,因为前面是拆分更大的的数,当j>i/2时开始拆分小数,拆分大的数得到的乘积会更大。

代码如下:

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

96.不同的二叉搜索树

思路:dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索树的数量 + 元素3为头结点搜索树的数量

元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量

元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量

元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量

所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]

动规五部曲

1、确定dp数组(dp table)以及下标的含义
dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]。

2、确定递推公式
在上面的分析中,其实已经看出其递推关系, dp[i] += dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量] (j相当于是头结点的元素,从1遍历到i为止。)

所以递推公式:dp[i] += dp[j - 1] * dp[i - j]; ,j-1 为j为头结点左子树节点数量,i-j 为以j为头结点右子树节点数量。

3、dp数组如何初始化
初始化,只需要初始化dp[0]就可以了,从定义上来讲,空节点也是一棵二叉树,也是一棵二叉搜索树。初始化dp[0] = 1

4、确定遍历顺序
从递归公式:dp[i] += dp[j - 1] * dp[i - j]可以看出,节点数为i的状态是依靠 i之前节点数的状态。

5、举例推导dp数组

代码如下:

class Solution {
    public int numTrees(int n) {
        int[] dp=new int[n+1];
        dp[0]=1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=i;j++){
                dp[i]+=dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }
}

注:代码虽然看起来很简单,但思路并不容易想到,应熟悉掌握思路的形成方式以及动规五部曲的使用。

标签:结点,遍历,int,不同,路径,搜索,为头,dp,96
From: https://blog.csdn.net/m0_51007517/article/details/142624342

相关文章