文章目录
三角形最小路径和
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
示例 1:
输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
示例 2:
输入:triangle = [[-10]]
输出:-10
动态规划
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[] dp = new int[n];
dp[0] = triangle.get(0).get(0);
for (int i = 1; i < n; i++) {
List<Integer> row = triangle.get(i);
dp[i] = dp[i - 1] + row.get(i);
for (int j = i - 1; j > 0; --j)
dp[j] = Math.min(dp[j - 1], dp[j]) + row.get(j);
dp[0] += row.get(0);
}
int min = Integer.MAX_VALUE;
for (int m : dp)
min = Math.min(min, m);
return min;
}
}
最大正方形
在一个由 ‘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 rows = matrix.length, cols = matrix[0].length;
int max_length = 0;
for (int i = 0; i < rows - max_length; i++) {
for (int j = 0; j < cols - max_length; j++) {
int length = 0;
while (checkSquare(matrix, i, j, length)) {
length++;
if (i + length >= rows || j + length >= cols)
break;
}
if(length > max_length)
max_length = length;
}
}
return max_length * max_length;
}
public boolean checkSquare(char[][] matrix, int i, int j, int length) {
for(int k = 0; k <= length; k++) {
if(matrix[i + k][j + length] == '0' || matrix[i + length][j + k] == '0')
return false;
}
return true;
}
}
动态规划
class Solution {
public int maximalSquare(char[][] matrix) {
int rows = matrix.length, columns = matrix[0].length;
int max_length = 0;
int[][] dp = new int[rows][columns];
for (int i = 0; i < rows - max_length; i++) {
for (int j = 0; j < columns - max_length; j++) {
if (matrix[i][j] == '1') {
if (i == 0 || j == 0)
dp[i][j] = 1;
else
dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
max_length = Math.max(max_length, dp[i][j]);
}
}
}
return max_length * max_length;
}
}
最小路径和
给定一个包含非负整数的 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 rows = grid.length, columns = grid[0].length;
for (int i = 1; i < columns; i++)
grid[0][i] += grid[0][i - 1];
for (int i = 1; i < rows; i++)
grid[i][0] += grid[i - 1][0];
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++)
grid[i][j] += Math.min(grid[i][j - 1], grid[i - 1][j]);
}
return grid[rows-1][columns-1];
}
}
地下城游戏
一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。
骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。
有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。
为了尽快到达公主,骑士决定每次只向右或向下移动一步。
编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。
例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。
DFS
深度优先搜索,遍历所有路线。会有很多重复计算。
class Solution {
public int calculateMinimumHP(int[][] dungeon) {
return dfs(dungeon, 0, 0, dungeon.length, dungeon[0].length);
}
private int dfs(int[][] dungeon, int i, int j, int rows, int cols) {
if (i == rows - 1 && j == cols - 1)
return Math.max(1 - dungeon[i][j], 1);
if (i == rows - 1)
return Math.max(dfs(dungeon, i, j + 1, rows, cols) - dungeon[i][j], 1);
if (j == cols - 1)
return Math.max(dfs(dungeon, i + 1, j, rows, cols) - dungeon[i][j], 1);
return Math.max(Math.min(dfs(dungeon, i + 1, j, rows, cols), dfs(dungeon, i, j + 1, rows, cols)) - dungeon[i][j], 1);
}
}
DFS+记忆化
构造一个 memory 数组,用来存放已经计算过的结果。
class Solution {
private int[][] memory;
public int calculateMinimumHP(int[][] dungeon) {
int rows = dungeon.length, cols = dungeon[0].length;
memory = new int[rows][cols];
return dfs(dungeon, 0, 0, rows, cols);
}
private int dfs(int[][] dungeon, int i, int j, int rows, int cols) {
if (i == rows - 1 && j == cols - 1)
return Math.max(1 - dungeon[i][j], 1);
if (memory[i][j] > 0)
return memory[i][j];
int min = 0;
if (i == rows - 1)
min = Math.max(dfs(dungeon, i, j + 1, rows, cols) - dungeon[i][j], 1);
else if (j == cols - 1)
min = Math.max(dfs(dungeon, i + 1, j, rows, cols) - dungeon[i][j], 1);
else
min = Math.max(Math.min(dfs(dungeon, i + 1, j, rows, cols), dfs(dungeon, i, j + 1, rows, cols)) - dungeon[i][j], 1);
memory[i][j] = min;
return min;
}
}
动态规划
class Solution {
public int calculateMinimumHP(int[][] dungeon) {
int rows = dungeon.length, cols = dungeon[0].length;
int[][] dp = new int[rows + 1][cols + 1];
for (int i = 0; i <= cols; ++i)
dp[rows][i] = Integer.MAX_VALUE;
for (int i = 0; i <= rows; ++i)
dp[i][cols] = Integer.MAX_VALUE;
dp[rows][cols - 1] = dp[rows - 1][cols] = 1;
for (int i = rows - 1; i >= 0; --i) {
for (int j = cols - 1; j >= 0; --j) {
int min = Math.min(dp[i][j + 1], dp[i + 1][j]);
dp[i][j] = Math.max(min - dungeon[i][j], 1);
}
}
return dp[0][0];
}
}
不同路径1
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
向右 -> 向下 -> 向下
向下 -> 向下 -> 向右
向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
动态规划
二维数组存储
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
Arrays.fill(dp[0], 1);
for (int i = 1; i < m; i++)
dp[i][0] = 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];
}
}
一维数组存储
class Solution {
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
dp[0] = 1;
for (int i = 0; i < m; i++) {
for (int j = 1; j < n; j++)
dp[j] += dp[j - 1];
}
return dp[n - 1];
}
}
组合数学
从左上角到右下角的过程中,我们需要移动 m+n-2 次,其中有 m-1 次向下移动,n−1 次向右移动。因此路径的总数,就等于从 m+n-2 次移动中选择 m−1 次向下移动的方案数,即组合数:
C
m
+
n
−
2
m
−
1
=
(
m
+
n
−
2
m
−
1
)
=
(
m
+
n
−
2
)
(
m
+
n
−
3
)
⋯
n
(
m
−
1
)
!
=
(
m
+
n
−
2
)
!
(
m
−
1
)
!
(
n
−
1
)
!
C_{m+n-2}^{m-1}=\left(\begin{array}{c}m+n-2 \\ m-1\end{array}\right)=\frac{(m+n-2)(m+n-3) \cdots n}{(m-1) !}=\frac{(m+n-2) !}{(m-1) !(n-1) !}
Cm+n−2m−1=(m+n−2m−1)=(m−1)!(m+n−2)(m+n−3)⋯n=(m−1)!(n−1)!(m+n−2)!
class Solution {
public int uniquePaths(int m, int n) {
long result = 1;
for (int x = n, y = 1; y < m; ++x, ++y)
result = result * x / y;
return (int) ans;
}
}
不同路径2
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
示例 1:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
向右 -> 向右 -> 向下 -> 向下
向下 -> 向下 -> 向右 -> 向右
示例 2:
输入:obstacleGrid = [[0,1],[0,0]]
输出:1
动态规划
二维数组存储
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length, n = obstacleGrid[0].length;
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++)
dp[i][j] = (obstacleGrid[i][j] == 1) ? 0 : dp[i - 1][j] + dp[i][j - 1];
}
return dp[m - 1][n - 1];
}
}
动态规划2
一维数组存储
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length, n = obstacleGrid[0].length;
int[] dp = new int[n];
dp[0] = obstacleGrid[0][0] == 0 ? 1 : 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
dp[j] = 0;
continue;
}
if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0)
dp[j] += dp[j - 1];
}
}
return dp[n - 1];
}
}
矩阵中的最长递增路径
给定一个 m x n 整数矩阵 matrix ,找出其中 最长递增路径 的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外(即不允许环绕)。
示例 1:
输入:matrix = [[9,9,4],[6,6,8],[2,1,1]]
输出:4
解释:最长递增路径为 [1, 2, 6, 9]。
示例 2:
输入:matrix = [[3,4,5],[3,2,6],[2,2,1]]
输出:4
解释:最长递增路径是 [3, 4, 5, 6]。注意不允许在对角线方向上移动。
示例 3:
输入:matrix = [[1]]
输出:1
记忆化DFS
class Solution {
private int m, n;
private int[][] dp;
private int[][] directions = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
public int longestIncreasingPath(int[][] matrix) {
m = matrix.length; n = matrix[0].length;
dp = new int[m][n];
int count = 0;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
count = Math.max(count, dfs(matrix, i, j));
return count;
}
private int dfs(int[][] matrix, int r, int c) {
if (dp[r][c] > 0)
return dp[r][c];
dp[r][c]++;
for (int[] direction : directions) {
int x = r + direction[0], y = c + direction[1];
if (x < 0 || x >= m || y < 0 || y >= n || matrix[r][c] >= matrix[x][y])
continue;
dp[r][c] = Math.max(dp[r][c], dfs(matrix, x, y) + 1);
}
return dp[r][c];
}
}
出界的路径数
给定一个 m × n 的网格和一个球。球的起始坐标为 (i,j) ,你可以将球移到相邻的单元格内,或者往上、下、左、右四个方向上移动使球穿过网格边界。但是,你最多可以移动 N 次。找出可以将球移出边界的路径数量。
示例 1:
输入: m = 2, n = 2, N = 2, i = 0, j = 0
输出: 6
解释:
示例 2:
输入: m = 1, n = 3, N = 3, i = 0, j = 1
输出: 12
解释:
动态规划
class Solution {
private int mod = 1000000007;
private int[][] directions = new int[][] {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
public int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
int[][][] dp = new int[m][n][maxMove + 1];
return dfs(dp, m, n,startRow, startColumn, maxMove);
}
private int dfs(int[][][] dp, int m, int n, int i, int j, int maxMove) {
if (i < 0 || i >= m || j < 0 || j >= n)
return 1;
else if (dp[i][j][maxMove] > 0)
return dp[i][j][maxMove];
if (i >= maxMove && i < m - maxMove && j >= maxMove && j < n - maxMove)
return 0;
for (int[] dir : directions) {
dp[i][j][maxMove] += dfs(dp, m, n, i + dir[0], j + dir[1], maxMove - 1);
dp[i][j][maxMove] %= mod;
}
return dp[i][j][maxMove];
}
}