1. 不同路径
. - 力扣(LeetCode)
1.1 算法原理
- 状态表示dp[i][j]:走到(i,j)位置,一共有多少种方法(以(i,j)位置为结尾)
- 状态转移方程:dp[i][j]=dp[i-1][j]+dp[i][j-1];
- 初始化:dp[0][1]=1;
- 建表顺序:从上往下的每一行填表,每一行中从左往右。
- 返回值:dp[m][n]
1.2 算法代码
class Solution {
//1.创建dp表
//2.初始化
//3.填表顺序
//4.返回值
public int uniquePaths(int m, int n) {
int[][] dp = new int[m + 1][n + 1];
//初始化
dp[0][1] = 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][n];
}
}
2. 不同路径 II
. - 力扣(LeetCode)
2.1 算法原理
本题算法思想和上题基本一致,但需额外注意以下几点:
- dp填表时,需要注意当前位置(i,j)是否存在障碍物,若存在障碍物则dp[i][j]=0;
- 初始化后,虚拟节点值的设置与上题相同。但,由于本题是数组形式,所以需要额外注意原数组与dp表的下标映射关系。
2.2 算法代码
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
//以(i,j)位置为终点,到达(i,j)位置的路径总数
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m + 1][n + 1];
dp[0][1] = 1;
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
if(obstacleGrid[i - 1][j - 1] != 1)
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m][n];
}
}
3. 珠宝的最高价值
. - 力扣(LeetCode)
3.1 算法原理
- dp[i][j]状态表示:到达(i,j)位置,能得到的最高价值(以(i,j)位置为结尾)
- 状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + arr[i - 1][j - 1];
- 初始化: dp=new int[m+1][n+1];
- 虚拟节点值为0(不影响后续填表)
- 下标映射 dp(i,j)-->arr(i-1,j-1)
- 返回值:dp[m][n]
3.2 算法代码
class Solution {
public int jewelleryValue(int[][] frame) {
//dp[i][j]:以(i,j)位置为结尾,到达(i,j)位置得到的最高价值数
int m = frame.length;
int n = frame[0].length;
int[][] dp = new int[m + 1][n + 1];
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
//根据最近的一步划分问题 --> 状态转移方程
int val = Math.max(dp[i - 1][j], dp[i][j - 1]) + frame[i - 1][j - 1];
dp[i][j] = val;
}
}
return dp[m][n];
}
}
4. 下降路径最小和
. - 力扣(LeetCode)
4.1 算法原理
- 状态表示dp[i][j]:达到(i,j)位置,最小下降路径(以(i,j)位置为结尾)
- 状态转移方程:dp[i][j]=min(dp[左斜上],dp[正上],dp[右斜上])+arr[i-1][j-1];
- 初始化:dp表,多创建一行,多创建两列。
- 虚拟节点的初始值要保证后续填表的正确性:第一行 -> 0; 第一列、最后一列 -> MAX_V
- 下标映射
- 建表顺序:从上往下
- 返回值:dp表中最后一行的最小值
4.2 算法代码
class Solution {
public int minFallingPathSum(int[][] matrix) {
int n = matrix.length;
int[][] dp = new int[n + 1][n + 2];
for(int i = 0; i < n + 1; i++) Arrays.fill(dp[i], Integer.MAX_VALUE);
//初始化
for(int i = 0; i < n + 2; i++) dp[0][i] = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
int x1 = Math.min(Math.min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i - 1][j + 1]);
dp[i][j] = x1 + matrix[i - 1][j - 1];
}
}
int ret = Integer.MAX_VALUE;
for(int i = 1; i <= n; i++) ret = Math.min(ret, dp[n][i]);
return ret;
}
}
5. 最小路径和
. - 力扣(LeetCode)
5.2 算法原理
- 状态表示dp[i][j]:从起点开始,到达(i,j)位置的最小路径和(以(i,j)位置为结尾)
- 状态转移方程:dp[i][j]=min(dp[i-1][j], dp[i][j-1])+arr[i-1][j-1];
- 建表顺序:从上往下,从左往右
5.3 算法代码
class Solution {
public int minPathSum(int[][] grid) {
//1. 建dp表
//2. 初始化
//3. 填表顺序
//4. 返回值
int m = grid.length, n = grid[0].length;
int[][] dp = new int[m + 1][n + 1];
//初始化
for(int i = 0; i <= m; i++) Arrays.fill(dp[i], Integer.MAX_VALUE);
dp[1][0] = 0; dp[0][1] = 0;
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 - 1][j - 1];
}
}
return dp[m][n];
}
}
6. 地下城游戏
. - 力扣(LeetCode)
6.1 算法原理
状态表示dp[i][j]:
- 以(i,j)位置为结尾,到达(i,j)位置所需的最低生命值(dp[i][j]的值与后面路径的dp值有关,故该方法不可行)
- 以(i,j)位置为起点,到达终点所需的最低生命值(可行)
状态转移方程:
- dp[i][j]=min(dp[i+1][j], dp[i][j+1]) - arr[i][j];(从(i,j)位置走出时的生命值,要大于等于进入下一位置所需的生命值,x+arr[i][j]>=dp[i+1][j](或者x+arr[i][j]>=dp[i][j+1]) --> x>=dp[i+1][j]-arr[i][j])
- dp[i][j] = max(1, dp[i][j]);//本位置能够增加的生命值过多,导致计算出的值为负数,此时初始生命值可以为1
6.2 算法代码
class Solution {
public int calculateMinimumHP(int[][] dungeon) {
int m = dungeon.length, n = dungeon[0].length;
int[][] dp = new int[m + 1][n + 1];
for(int i = 0; i <= m; i++) dp[i][n] = Integer.MAX_VALUE;
for(int j = 0; j <= n; j++) dp[m][j] = Integer.MAX_VALUE;
//骑士拯救公主后生命值最少为1
dp[m][n - 1] = dp[m - 1][n] = 1;
for(int i = m - 1; i >= 0; i--) {
for(int j = n - 1; j >= 0; j--) {
int x = Math.min(dp[i + 1][j], dp[i][j + 1]);
//当dungeon[i][j]为过大正数时,x - dungeon[i][j]计算得到的是负数
//此时骑士的初始值可以为1
dp[i][j] = Math.max(1, x - dungeon[i][j]);
}
}
return dp[0][0];
}
}
END