持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情
本文包含下面几题:
- 给定一个网格和一个机器人,网格中包含障碍物,现在要求机器人从左上角到右下角,问有多少种方法。
- 给定一个整数,将其拆分位
k个正整数的和,使得这些整数成绩最大化。 - 求由
n个结点组成的二叉搜索数的数量。
解题思路
LeetCode 63 不同路径II
和之前机器人走路那题一样,但本题多了障碍物,一旦最终终点是障碍物,那直接返回0即可。否则还是和之前推导一样,如果当前位置的左边有障碍物,那只能从上面过来,如果上面有障碍物,那只能从左边过来,如果两边都存在障碍物,那该点就是不可达,可的判断语句如下:
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(obstacleGrid[i-1][j]==1&&obstacleGrid[i][j-1]==1){
dp[i][j] = 0;
}else if(obstacleGrid[i-1][j]==1){
dp[i][j] = dp[i][j-1];
}else if(obstacleGrid[i][j-1]==1){
dp[i][j] = dp[i-1][j];
}else {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
但实际上,当我们到达的点如果是障碍物,那直接continue即可,此时默认值即为0,最终代码如下:
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if(obstacleGrid[m-1][n-1]==1) return 0;
int[][] dp = new int[m][n];
for(int i=0;i<n;i++){
if(obstacleGrid[0][i]==1) break;
dp[0][i] = 1;
}
for(int i=0;i<m;i++){
if(obstacleGrid[i][0]==1) break;
dp[i][0] = 1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(obstacleGrid[i][j]==1) continue;
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
LeetCode 343、整数拆分
整数拆分,那拆分后的整数还可以再一次进行拆分,如果能确定每个整数拆分后的乘积最大值,那本题也就容易了,根据此思路可确定最终方法采用动态规划。
我们设定dp[i]代表将整数i拆分后的乘积最大值,对于整数i(由题可知i必然大于等于2,否则将无意义),我们可以将其拆为从1~i-1到i的数,假设1~i-1范围的数为j,则最终的最大值为:
此处之所以不是是因为代表的是整数i拆解后的最大值,其最终并不一定代表乘积最大,如果使用dp[j]则会丢失j这种情况。
最终可得代码如下:
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-1;j++){
dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
}
}
return dp[n];
}
LeetCode 96、不同的二叉搜索树
通过观察我们可以发现,如果一个结点当根节点,那么其左右子树的数量是确定的,并且左右子树组成的二叉搜索树和数值无关,可得出当j作为根节点,那可能组成的二叉树共有个,这一点可在n=3时验证,最终可得代码(注意注释):
public int numTrees(int n) {
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i=2;i<n+1;i++){
for(int j=1;j<=i;j++){
// 对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
// 一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
dp[i] += dp[j-1] * dp[i-j];
}
}
return dp[n];
}