求两字符串的最长公共子序列
- [[最长子序列]]
- 子序列可以不连续,子串必须连续
- 思路:样本对应模型先把数组对应成dp表,填basecase。找普遍的位置依赖
- process(i,j)表示arr[i],buf[j]之前最长的公共子序列。此时可能以arr[i]结尾,以buf结尾,不以这两端结尾,同时以两端结尾
最短路径和问题
- 题目:从二位数组在左上走到右下,只能向左,下走
- 思路:从最下行的右往左填。每次选min(右,下)
- 优化:因为每个位置依赖本行和下行->两行数组
- 优化:因为每个位置依赖的本行右侧不回退,所以只用一行,右边直接覆盖即可
-
package class21; public class Code01_MinPathSum { public static int minPathSum1(int[][] m) { if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) { return 0; } int row = m.length; int col = m[0].length; int[][] dp = new int[row][col]; dp[0][0] = m[0][0]; for (int i = 1; i < row; i++) { dp[i][0] = dp[i - 1][0] + m[i][0]; } for (int j = 1; j < col; j++) { dp[0][j] = dp[0][j - 1] + m[0][j]; } for (int i = 1; i < row; i++) { for (int j = 1; j < col; j++) { dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + m[i][j]; } } return dp[row - 1][col - 1]; } public static int minPathSum2(int[][] m) { if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) { return 0; } int row = m.length; int col = m[0].length; int[] dp = new int[col]; dp[0] = m[0][0]; for (int j = 1; j < col; j++) { dp[j] = dp[j - 1] + m[0][j]; } for (int i = 1; i < row; i++) { dp[0] += m[i][0]; for (int j = 1; j < col; j++) { dp[j] = Math.min(dp[j - 1], dp[j]) + m[i][j]; } } return dp[col - 1]; } // for test public static int[][] generateRandomMatrix(int rowSize, int colSize) { if (rowSize < 0 || colSize < 0) { return null; } int[][] result = new int[rowSize][colSize]; for (int i = 0; i != result.length; i++) { for (int j = 0; j != result[0].length; j++) { result[i][j] = (int) (Math.random() * 100); } } return result; } // for test public static void printMatrix(int[][] matrix) { for (int i = 0; i != matrix.length; i++) { for (int j = 0; j != matrix[0].length; j++) { System.out.print(matrix[i][j] + " "); } System.out.println(); } } public static void main(String[] args) { int rowSize = 10; int colSize = 10; int[][] m = generateRandomMatrix(rowSize, colSize); System.out.println(minPathSum1(m)); System.out.println(minPathSum2(m)); } }
K步超出矩阵概率
- 题目:人在(row,col)走k步求超出[N][M]矩阵的概率。中途超出也算
- 思路:当前位置直接调往四周走,K-1的情况。最后获得不超出的次数。总次数为4^k。
- base case:只要当前坐标超出[N][M]直接返回-1,上层处理-1的情况
- 传入:(row,col),K
- 决策:往四周走
- 传出:(row,col),K
- dp:有三个参数需要改三维dp类似 [[象棋跳马问题]],k为层数
- 预处理:0层的,[M][N]都填一
- 普遍位置依赖 dpk-1层的四周位置
- 代码
-
public static double livePosibility2(int row, int col, int k, int N, int M) { long[][][] dp = new long[N][M][k + 1]; for (int i = 0; i < N; i++) { for (int j = 0; j < M; j++) { dp[i][j][0] = 1; } } for (int rest = 1; rest <= k; rest++) { for (int r = 0; r < N; r++) { for (int c = 0; c < M; c++) { dp[r][c][rest] = pick(dp, N, M, r - 1, c, rest - 1); dp[r][c][rest] += pick(dp, N, M, r + 1, c, rest - 1); dp[r][c][rest] += pick(dp, N, M, r, c - 1, rest - 1); dp[r][c][rest] += pick(dp, N, M, r, c + 1, rest - 1); } } } return (double) dp[row][col][k] / Math.pow(4, k); }
人杀死怪兽的概率
- 题目:人发动K次攻击,每次打击[0~M]等概率的血。怪的血量为N
- 思路:每次攻击都枚举[0~M]攻击,往后传k–,M,n-相应的值。统计怪没死的次数。all=(M+1)^K
- 传入:K,M,N
- base case:N<=0 retuen 0,k=0&&N>0 return 1
- 决策:对当前的K,M,N,枚举0~M的伤害值。接收每次调用的返回值,汇总后网上传
- 传出:枚举行为调用相应的KMN
- dp:这里有枚举行为所以要建严格的表结构处理依赖,只需要建立(K,N)的表
- 预处理:(这里不好在格中填存活的次数,所以填死亡的次数),N=0列的死亡次数等于m^K
- 普遍位置依赖:dp[k-1][n-0]~dp[k-1][n-m],(n-m>=1)
代码
public static double dp2(int N, int M, int K) {
if (N < 1 || M < 1 || K < 1) {
return 0;
}
long all = (long) Math.pow(M + 1, K);
long[][] dp = new long[K + 1][N + 1];
dp[0][0] = 1;
for (int times = 1; times <= K; times++) {
dp[times][0] = (long) Math.pow(M + 1, times);
for (int hp = 1; hp <= N; hp++) {
dp[times][hp] = dp[times][hp - 1] + dp[times - 1][hp];
if (hp - 1 - M >= 0) {
dp[times][hp] -= dp[times - 1][hp - 1 - M];
} else {
dp[times][hp] -= Math.pow(M + 1, times - 1);
}
}
}
long kill = dp[K][N];
return (double) ((double) kill / (double) all);
}
给定一个正数求裂开的方法数
- 题目:把一个正数才成其他正数,求组合
- 给定一个正数1,裂开的方法有一种,(1) 给定一个正数2,裂开的方法有两种,(1和1)、(2) 给定一个正数3,裂开的方法有三种,(1、1、1)、(1、2)、(3) 给定一个正数4,裂开的方法有五种,(1、1、1、1)、(1、1、2)、(1、3)、(2、2)、 (4)
给定一个正数n,求裂开的方法数。
- 给定一个正数1,裂开的方法有一种,(1) 给定一个正数2,裂开的方法有两种,(1和1)、(2) 给定一个正数3,裂开的方法有三种,(1、1、1)、(1、2)、(3) 给定一个正数4,裂开的方法有五种,(1、1、1、1)、(1、1、2)、(1、3)、(2、2)、 (4)
- 思路:两个参数,上一个裂出来的数pre,还剩的数rest。rest枚举所有减去pre的情况,因为相同值的算一个数(不用全排列),所以pre从小加到大
- 传入:pre,rest
- base case:rest-pre=0时 return 1;rest-pre<0 时 return 0;
- 决策:枚举rest减去所有次数的pre的情况
- 传出:newPre,newRest
-
代码
```java public static int dp1(int n) { if (n < 0) { return 0; } if (n == 1) { return 1; } int[][] dp = new int[n + 1][n + 1]; for (int pre = 1; pre <= n; pre++) { dp[pre][0] = 1; dp[pre][pre] = 1; } for (int pre = n - 1; pre >= 1; pre--) { for (int rest = pre + 1; rest <= n; rest++) { int ways = 0; for (int first = pre; first <= rest; first++) { ways += dp[first][rest - first]; } dp[pre][rest] = ways; } } return dp[1][n]; } ```
- 存在枚举行为,所以可以用严格的表结构来优化(pre,rest)建表
- 普遍位置的表依赖:一条斜线,找到能某个已经计算过部分斜线的格子
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHf0JsYg-1663668124568)(…/assets/image_1641963812307_0.png)]
public static int dp2(int n) {
if (n < 0) {
return 0;
}
if (n == 1) {
return 1;
}
int[][] dp = new int[n + 1][n + 1];
for (int pre = 1; pre <= n; pre++) {
dp[pre][0] = 1;
dp[pre][pre] = 1;
}
for (int pre = n - 1; pre >= 1; pre--) {
for (int rest = pre + 1; rest <= n; rest++) {
dp[pre][rest] = dp[pre + 1][rest];
dp[pre][rest] += dp[pre][rest - pre];
}
}
return dp[1][n];
}