写在前边:就我而言:矩阵的动态规划显得更加清晰,写的很顺手,就是数组别越界更好了:(
1.1 不同路径
题目描述:一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步,试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7 输出:28
示例 2:
输入:m = 3, n = 2 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 3. 向下 -> 向右 -> 向下
代码部分:
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 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-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
思路:
使用了一个二维数组dp
来记录到达每个位置的不同路径数量。首先,将第一行和第一列的路径数量初始化为1,因为只有一条路径可以到达这些位置。
然后,从第二行和第二列开始,通过状态转移方程dp[i][j] = dp[i-1][j] + dp[i][j-1]
来计算每个位置的路径数量。其中,dp[i-1][j]
表示从上方位置到达当前位置的路径数量,dp[i][j-1]
表示从左方位置到达当前位置的路径数量。由于只能向右或向下移动一步,所以到达当前位置的路径数量等于从上方和左方位置的路径数量之和。
最后,返回dp[m-1][n-1]
,即右下角位置的路径数量,即为问题的解。
另一种思路:具体来说,机器人需要向右移动n-1次,向下移动m-1次,总共移动n-1+m-1=n+m-2次,因此不同路径的数量可以表示为C(n+m-2, n-1)或C(n+m-2, m-1)。
因此,可以直接使用组合数公式来计算不同路径的数量,而不需要使用动态规划。
class Solution {
public int uniquePaths(int m, int n) {
int N = n + m - 2; // 总共的移动次数
int k = n - 1; // 向下的移动次数
long res = 1;
for (int i = 1; i <= k; i++) {
res = res * (N - k + i) / i;
}
return (int) res;
}
}
1.2 不同路径Ⅱ
题目描述:一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1
和 0
来表示。
示例 1:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2
条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
示例 2:
输入:obstacleGrid = [[0,1],[0,0]] 输出:1
代码部分:
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
// 处理起点就是障碍物的情况
if (obstacleGrid[0][0] == 1) {
return 0;
}
dp[0][0] = 1;
// 初始化第一列的路径数
for (int i = 1; i < m; i++) {
if (obstacleGrid[i][0] == 1) {
dp[i][0] = 0;
} else {
dp[i][0] = dp[i - 1][0];
}
}
// 初始化第一行的路径数
for (int j = 1; j < n; j++) {
if (obstacleGrid[0][j] == 1) {
dp[0][j] = 0;
} else {
dp[0][j] = 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;
} else {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
return dp[m - 1][n - 1];
}
}
思路:
使用了一个二维数组dp
来记录到达每个位置的不同路径数量。首先,处理起点就是障碍物的情况,如果起点是障碍物,直接返回路径数量为0。
然后,初始化第一行和第一列的路径数量,如果某个位置是障碍物,路径数量为0,否则等于其前一个位置的路径数量。
接下来,计算其他位置的路径数量。对于每个位置,如果该位置是障碍物,路径数量为0,否则等于上方位置和左方位置的路径数量之和。
最后,返回右下角位置的路径数量,即为问题的解。
1.3 最小路径和
题目描述:给定一个包含非负整数的 m x n
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 输出:7 解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]] 输出:12
代码部分:
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for(int i = 1 ; i < m ; i++){
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for(int j = 1 ; j < n ; j++){
dp[0][j] = dp[0][j-1] + grid[0][j];
}
for(int i = 1 ; i < m ; i++){
for(int 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];
}
}
思路:
使用了一个二维数组dp
来记录到达每个位置的最小路径和。首先,初始化起点位置的最小路径和为起点位置的权值。
然后,初始化第一行和第一列的最小路径和,由于只能向右和向下移动,所以第一行的最小路径和等于前一个位置的最小路径和加上当前位置的权值,第一列同理。
接下来,计算其他位置的最小路径和。对于每个位置,最小路径和等于上方位置和左方位置的最小路径和中的较小值加上当前位置的权值。
最后,返回右下角位置的最小路径和,即为问题的解。
1.4 三角形最小路径和
题目描述:给定一个三角形 triangle
,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i
,那么下一步可以移动到下一行的下标 i
或 i + 1
代码部分:
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[] dp = new int[n];
// 初始化最后一行的 dp 数组
List<Integer> lastRow = triangle.get(n - 1);
for (int i = 0; i < n; i++) {
dp[i] = lastRow.get(i);
}
// 从倒数第二行开始逐行计算最小路径和
for (int row = n - 2; row >= 0; row--) {
List<Integer> currentRow = triangle.get(row);
for (int col = 0; col <= row; col++) {
int minPathSum = Math.min(dp[col], dp[col + 1]) + currentRow.get(col);
dp[col] = minPathSum;
}
}
return dp[0]; // 最终结果存储在 dp[0] 中
}
}
思路:
假设给定的三角形如下所示:
2
3 4
6 5 7
4 1 8 3
按照题目要求,从顶部到底部的最小路径和为:2 + 3 + 5 + 1 = 11。
现在详细解释代码的逻辑:
首先,获取三角形的行数,并创建一个长度为 n 的一维数组 dp,用于存储每个位置的最小路径和。
初始化 dp 数组的最后一行,即三角形的底部。将底部行的元素依次赋值给 dp 数组的对应位置。
在示例中,三角形的底部行为 [4, 1, 8, 3],将其赋值给 dp 数组:dp = [4, 1, 8, 3]。
从倒数第二行开始,逐行计算最小路径和。
在示例中,从第三行开始计算。
对于每一行的每个元素,计算其下方两个相邻元素的最小路径和,并加上当前元素的值,更新到 dp 数组中。
在示例中,第三行的元素为 [6, 5, 7]。计算第三行每个元素的最小路径和:
对于第一个元素 6,下方相邻元素为 4 和 1。计算最小路径和为 min(4, 1) + 6 = 7。更新 dp 数组:dp = [7, 1, 8, 3]。
对于第二个元素 5,下方相邻元素为 1 和 8。计算最小路径和为 min(1, 8) + 5 = 6。更新 dp 数组:dp = [7, 6, 8, 3]。
对于第三个元素 7,下方相邻元素为 8 和 3。计算最小路径和为 min(8, 3) + 7 = 10。更新 dp 数组:dp = [7, 6, 10, 3]。
继续向上逐行计算,重复步骤 4,直到计算到顶部行。
在示例中,计算到第二行时,dp 数组为 dp = [9, 10, 10, 3]。
在计算到顶部行时,dp 数组的第一个元素即为从顶部到底部的最小路径和。
返回 dp[0],即最终的最小路径和。
在示例中,最终的最小路径和为 11,与预期结果相符。
这种方法利用了动态规划的思想,在计算过程中不断更新状态数组,最终得到最优解。时间复杂度为O(n^2),其中n是三角形的行数。空间复杂度为O(n),用于存储每行的最小路径和。
1.5 下降路径最小和
题目描述:给你一个 n x n
的 方形 整数数组 matrix
,请你找出并返回通过 matrix
的下降路径 的 最小和 。
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col)
的下一个元素应当是 (row + 1, col - 1)
、(row + 1, col)
或者 (row + 1, col + 1)
。
示例 1:
输入:matrix = [[2,1,3],[6,5,4],[7,8,9]] 输出:13 解释:如图所示,为和最小的两条下降路径
示例 2:
输入:matrix = [[-19,57],[-40,-5]] 输出:-59 解释:如图所示,为和最小的下降路径
代码部分:
class Solution {
public int minFallingPathSum(int[][] matrix) {
int n = matrix.length;
if(n == 1){
return matrix[0][0];
}
int[][] dp = new int[n][n];
for(int i = 0; i < n ; i++){
dp[0][i] = matrix[0][i];
}
for(int j = 1; j < n; j++){
for(int i = 0 ; i < n ; i++){
if(i == 0){
dp[j][i] = Math.min(dp[j-1][i],dp[j-1][i+1])+matrix[j][i];
}else if(i == n-1){
dp[j][i] = Math.min(dp[j-1][i],dp[j-1][i-1])+matrix[j][i];
}else{
dp[j][i] = Math.min(Math.min(dp[j-1][i],dp[j-1][i-1]),dp[j-1][i+1])+matrix[j][i];
}
}
}
int min = dp[n-1][0];
for(int i = 0; i < n ; i++){
if(dp[n-1][i]<min){
min = dp[n-1][i];
}
}
return min;
}
}
思路:
使用了一个二维数组dp
来记录每个位置的最小路径和。首先,将第一行的路径和初始化为对应位置的权值。
然后,从第二行开始逐行计算最小路径和。对于每个位置,最小路径和等于上一行相邻位置的最小路径和中的较小值加上当前位置的权值。根据位置的不同,需要考虑左上、正上和右上三个相邻位置的最小路径和。
最后,遍历最后一行的最小路径和,找到其中的最小值,即为问题的解。
1.6 最大正方形
题目描述:在一个由 '0'
'1'
组成的二维矩阵内,找到只包含 '1'
的最大正方形,返回其面积。
示例 1:
输入:matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]] 输出:4
示例 2:
输入:matrix = [["0","1"],["1","0"]] 输出:1
示例 3:
输入:matrix = [["0"]] 输出:0
代码部分:
class Solution {
public int maximalSquare(char[][] matrix) {
int[][] dp = new int[matrix.length][matrix[0].length];
int result = 0;
int max = 0;
for(int i = 0 ; i < matrix.length ; i++){
if(matrix[i][0] == '1'){
dp[i][0] = 1;
}
}
for(int j = 0 ; j < matrix[0].length ; j++){
if(matrix[0][j] == '1'){
dp[0][j] = 1;
}
}
for(int i = 1 ; i < matrix.length ; i++){
for(int j = 1 ; j < matrix[0].length ; j++){
if(matrix[i][j] == '0'){
dp[i][j] =0;
}else{
dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) + 1;
}
}
}
for(int i = 0 ; i < matrix.length ; i++){
for(int j = 0 ; j < matrix[0].length ; j++){
if(dp[i][j]>max){
max = dp[i][j];
}
}}
return max * max;
}
}
思路:
使用了一个二维数组dp
来记录以当前位置为右下角的最大正方形的边长。首先,将第一行和第一列的边长初始化为对应位置的值(如果为'1'则为1,否则为0)。
然后,从第二行和第二列开始逐个计算最大正方形的边长。对于每个位置,如果当前位置为'0',则以该位置为右下角的最大正方形的边长为0;如果当前位置为'1',则以该位置为右下角的最大正方形的边长取决于左边、上边和左上角位置的最小边长,再加上1。
最后,遍历dp
数组,找到其中的最大值(即最大正方形的边长),计算并返回面积。