dp动态规划详解上:https://blog.csdn.net/weixin_73936404/article/details/131527247?spm=1001.2014.3001.5501
目录
dp动态规划详解上:https://blog.csdn.net/weixin_73936404/article/details/131527247?spm=1001.2014.3001.5501
【案例1】
【题目描述】
【暴力递归 代码展示】
【展示如何改为动态规划】
【思路解析】
【代码实现】 通过对数器验证,代码正确
【案例2】
【题目描述】
【暴力递归 代码实现】
【动态规划 代码实现】
【案例3】//经过对数器校验,代码正确
【题目描述】
【暴力递归 代码实现】
【动态规划 代码实现】
【案例4】
【题目描述】
【暴力递归 代码实现】
【动态规划 代码实现 未优化版本】
【动态规划 优化版本】 斜率优化,如果有枚举这种策略,就可以使用这种优化策略。
【案例1】
【题目描述】
给定一个整型数组arr,代表数值不同的纸牌排成一条线
玩家A和玩家B依次拿走每张纸牌
规定玩家A先拿,玩家B后拿
但是每个玩家每次只能拿走最左或最右的纸牌
玩家A和玩家B都绝顶聪明
请返回最后获胜者的分数
【这道题暴力递归思路详见: https://blog.csdn.net/weixin_73936404/article/details/131162695?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22131162695%22%2C%22source%22%3A%22weixin_73936404%22%7D】
【暴力递归 代码展示】
/**
* @ProjectName: study3
* @FileName: Win
* @author:HWJ
* @Data: 2023/6/12 12:38
*/
public class Win {
public static void main(String[] args) {
int[] arr = {1,2,100,4};
System.out.println(win(arr));
}
public static int win(int[] arr){
if (arr == null || arr.length == 0){
return 0;
}
return Math.max(f(arr, 0, arr.length-1), s(arr, 0, arr.length-1));
}
public static int f (int[] arr, int l, int r){
if (l == r){ // 如果是最后一张排,因为是先手,就可以拿到这张牌,返回这张牌
return arr[l];
}
return Math.max(arr[l] + s(arr, l+1, r), arr[r] + s(arr, l, r-1));
}
public static int s (int[] arr, int l, int r){
if (l == r){ // 如果是最后一张排,因为是后手,就无法拿到这张牌,返回0
return 0;
}
// 返回对手已经拿了一张牌
// 此时我们便变为后手,
// 因为对面先手,一定会拿对自己有利的牌,我们就无法拿到有利的牌,所以我们只能拿到最小的牌中对自己有利的牌
return Math.min(f(arr, l+1, r), f(arr, l, r-1));
}
}
【展示如何改为动态规划】
【思路解析】
因为这是一个两个递归函数,所有我们需要构建两个dp数组,根据暴力递归我们知道,他们是通过函数互相调用来求解最大值的过程,所有两个数组要互相依靠才能填充数组。
每个数组根据basecase 把可以填充的数据填充完。数组大小是由可变参数的变化范围决定的。
例如:一个正数数组 3 100 4 50
则他们需要两个4*4的数组。且i<j 所以数组对角线以下的数据是无用数据不需要填充,对角线数据是basecase,所以可以直接填充,其余数据根据递归的调用关系进行填充。
【代码实现】 通过对数器验证,代码正确
/**
* @ProjectName: study3
* @FileName: WinDp
* @author:HWJ
* @Data: 2023/7/5 10:47
*/
public class WinDp {
public static void main(String[] args) {
int[] arr = {1,2,100,4};
System.out.println(getWin(arr));
}
public static int getWin(int[] arr) {
final int N = arr.length;
int[][] f = new int[N][N];
int[][] s = new int[N][N];
for (int i = 0; i < N; i++) { // 根据baseCase填充数组
f[i][i] = arr[i];
s[i][i] = 0;
}
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length - i; j++) {
// 这里就是讲递归填充 拆为数组填充
f[j][j + i] = Math.max(arr[j] + s[j + 1][j + i], arr[j + i] + s[j][j + i - 1]);
s[j][j + i] = Math.min(f[j + 1][j + i], f[j][j + i - 1]);
}
}
return Math.max(f[0][N - 1], s[0][N - 1]);
}
}
【案例2】
【题目描述】
描述:假设把整个棋盘放入第一象限,棋盘的最左下角是(0,0)位置,整个棋盘就是横坐标上9条线,纵坐标上10条线的区域,给出三个参数x,y,k;返回“马”从(0,0)位置出发,必须走K步,最终落在(x,y)上的方法数有多少种?
比如:K = 10;x = 7;y = 7 ,返回多少种方法? (中国象棋棋盘 10行9列)
【暴力递归 代码实现】
因为马的路线是可逆的,则从(0,0)走到(x,y)可以看作是(x,y)走到(0,0)
/**
* @ProjectName: study3
* @FileName: maxWays
* @author:HWJ
* @Data: 2023/7/5 11:45
*/
public class maxWays {
public static void main(String[] args) {
}
public static int getMax(int x, int y, int k) {
return ways(x, y, k);
}
public static int ways(int x, int y, int step) {
if (x < 0 || x > 8 || y < 0 || y > 9) { // 如果马跳出棋盘了,返回0
return 0;
}
if (step == 0) { // 如果已经步数为0了,如果跳到了(0,0)点则是一个有效路径
return (x == 0 && y == 0) ? 1 : 0;
}
// 从源头不断往前回溯
return ways(x + 2, y - 1, step - 1) +
ways(x + 2, y + 1, step - 1) +
ways(x + 1, y + 2, step - 1) +
ways(x - 1, y + 2, step - 1) +
ways(x - 2, y + 1, step - 1) +
ways(x - 2, y - 1, step - 1) +
ways(x + 1, y - 2, step - 1) +
ways(x - 1, y + 2, step - 1);
}
}
【动态规划 代码实现】
/**
* @ProjectName: study3
* @FileName: maxWays
* @author:HWJ
* @Data: 2023/7/5 11:45
*/
public class maxWays {
public static void main(String[] args) {
}
public static int dpWays(int x, int y, int k) {
int[][][] dp = new int[9][10][k + 1];
dp[0][0][0] = 1;
for (int i = 1; i < k + 1; i++) {
for (int j = 0; j < 9; j++) {
for (int l = 0; l < 10; l++) {
dp[j][l][i] += getValue(dp, j - 2, l + 1, i - 1);
dp[j][l][i] += getValue(dp, j - 2, l - 1, i - 1);
dp[j][l][i] += getValue(dp, j + 2, l + 1, i - 1);
dp[j][l][i] += getValue(dp, j + 2, l - 1, i - 1);
dp[j][l][i] += getValue(dp, j - 1, l + 2, i - 1);
dp[j][l][i] += getValue(dp, j + 1, l + 2, i - 1);
dp[j][l][i] += getValue(dp, j - 1, l - 2, i - 1);
dp[j][l][i] += getValue(dp, j + 1, l - 2, i - 1);
}
}
}
return dp[x][y][k];
}
public static int getValue(int[][][] dp, int x, int y, int z) {
if (x < 0 || x > 8 || y < 0 || y > 9) {
return 0;
}
return dp[x][y][z];
}
}
【案例3】//经过对数器校验,代码正确
【题目描述】
给出五个参数,m,n,a,b,k; m和n代表安全区的规格,a和b代表Bob当前所在位置, a和b可能一开始就不在安全区域,只要某一步不在安全区域就算死亡,k代表一定要走k步,它可以随机向上下左右任意一个方向走一步,计算他走k步后的生还可能性。所以如果他一开始就不在安全区域,那么他的生还几率一定为0
【暴力递归 代码实现】
/**
* @ProjectName: study3
* @FileName: BobWays
* @author:HWJ
* @Data: 2023/7/5 12:46
*/
public class BobWays {
public static void main(String[] args) {
}
public static String probability(int n, int m, int i, int j, int k) {
long all = (long) Math.pow(4, k);
long live = process(n, m, i, j, k);
long gcd = gcd(all, live);
return String.valueOf((live/gcd) + "/" + (all/gcd));
}
public static long process(int n, int m, int i, int j, int step) {
// 一旦越界就判定死亡
if (i < 0 || i >= n || j < 0 || j >= m) {
return 0;
}
//如果已经走完 且没有越界则 找到一种生还的可能
if (step == 0) {
return 1;
}
// 所有活下来的可能
long live = process(n, m, i - 1, j, step - 1);
live += process(n, m, i + 1, j, step - 1);
live += process(n, m, i, j + 1, step - 1);
live += process(n, m, i, j - 1, step - 1);
return live;
}
public static long gcd(long a, long b){ // 求最大公约数
return b == 0?a:gcd(b,a%b);
}
}
【动态规划 代码实现】
/**
* @ProjectName: study3
* @FileName: BobWays
* @author:HWJ
* @Data: 2023/7/5 12:46
*/
public class BobWays {
public static void main(String[] args) {
}
public static String dpProb(int n, int m, int i, int j, int k) {
long all = (long) Math.pow(4, k);
long[][][] dp = new long[n][m][k + 1];
for (int l = 0; l < n; l++) {
for (int o = 0; o < m; o++) {
dp[l][o][0] = 1;
}
}
for (int l = 1; l <= k; l++) {
for (int o = 0; o < n; o++) {
for (int p = 0; p < m; p++) {
dp[o][p][l] += getValue(dp, n, m, o - 1, p, l - 1);
dp[o][p][l] += getValue(dp, n, m, o, p - 1, l - 1);
dp[o][p][l] += getValue(dp, n, m, o + 1, p, l - 1);
dp[o][p][l] += getValue(dp, n, m, o, p + 1, l - 1);
}
}
}
long live = 0;
if (i > 0 && i < n && j > 0 && j < m) {
live = dp[i][j][k];
}
long gcd = gcd(all, live);
return String.valueOf((live / gcd) + "/" + (all / gcd));
}
//得到有效数据,越界就返回0
public static long getValue(long[][][] dp, int n, int m, int i, int j, int k) {
if (i < 0 || i >= n || j < 0 || j >= m) {
return 0;
}
return dp[i][j][k];
}
public static long gcd(long a, long b) { // 求最大公约数
return b == 0 ? a : gcd(b, a % b);
}
}
【案例4】
// 使用对数器校验代码, 代码正确
【题目描述】
给出一个正数数组,里面正数不重复,每一个数代表一种面值的纸币,有任意多张,然后给出一个数,我们使用这些纸币组成这个数有多少种方法。
【暴力递归 代码实现】
/**
* @ProjectName: study3
* @FileName: ChangeMoney
* @author:HWJ
* @Data: 2023/7/5 14:07
*/
public class ChangeMoney {
public static void main(String[] args) {
int[] arr = {3, 5, 10, 1};
System.out.println(change(arr, 1000));
}
public static long change(int[] arr, int money) {
return process(arr, 0, money);
}
// arr是一个正数数组,里面的数字不重复,你可以无限使用同一个面值的纸币,要求最终能找零money
// 求出总方法
public static long process(int[] arr, int index, int rest) {
if (index == arr.length) {
return rest == 0 ? 1 : 0;
}
long ways = 0;
for (int i = 0; i * arr[index] <= rest; i++) {
ways += process(arr, index + 1, rest - i * arr[index]);
}
return ways;
}
}
【动态规划 代码实现 未优化版本】
/**
* @ProjectName: study3
* @FileName: ChangeMoney
* @author:HWJ
* @Data: 2023/7/5 14:07
*/
public class ChangeMoney {
public static void main(String[] args) {
}
public static long change2(int[] arr, int money) {
int N = arr.length;
long[][] dp = new long[N + 1][money + 1];
dp[N][0] = 1;
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= money; rest++) {
long ways = 0;
for (int i = 0; i * arr[index] <= rest; i++) {
ways += dp[index + 1][rest - i * arr[index]];
}
dp[index][rest] = ways;
}
}
return dp[0][money];
}
}
【动态规划 优化版本】 斜率优化,如果有枚举这种策略,就可以使用这种优化策略。
可以利用同层信息加速
/**
* @ProjectName: study3
* @FileName: ChangeMoney
* @author:HWJ
* @Data: 2023/7/5 14:07
*/
public class ChangeMoney {
public static void main(String[] args) {
}
public static long change3(int[] arr, int money) {
int N = arr.length;
long[][] dp = new long[N + 1][money + 1];
dp[N][0] = 1;
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= money; rest++) {
dp[index][rest] = dp[index-1][rest];
if (rest - arr[index] >= 0){
dp[index][rest] += dp[index][rest - arr[index]];
}
}
}
return dp[0][money];
}
}