目录
递归尝试->记忆化搜索->动态规划
暴力递归有重复计算,二叉展开,时间复杂度O(2^k)
记忆化搜索:递归时带入一张表,先获取表中信息,没计算过为-1,遇到重复计算直接获取答案 时间复杂度O(K*N)
递归(尝试)->记忆化搜索(加入缓存)->动态规划: 1、分析可变参数变化范围 2、标出计算的终止位置 3、标出不用计算就可知道的答案 4、普遍位置是如何依赖其他位置 5、确定计算顺序
机器人移动
给定1~N个长度,机器人初始在start位置,每一步必须移动,经过k步到达end的方法有多少种。
package com.wtp.基础提升.暴力递归改动态规划; public class 机器人移动 { /** * * @param N 1~N的位置 * @param E 最终到达E * @param rest 还剩多少步 * @param cur 当前位置 * @return */ public static int f1(int N,int E,int rest,int cur) {//二叉展开 时间复杂度O(2^rest) if(rest == 0) { return cur == E ? 1 : 0; } if(cur == 1) { return f1(N,E,rest - 1,2); } if(cur == N) { return f1(N,E,rest - 1,N - 1); } return f1(N,E,rest - 1,cur + 1) + f1(N,E,rest - 1,cur - 1); } public static int w(int N,int E,int rest,int cur){ int[][] dp = new int[N+1][rest+1]; for(int i = 0;i <= N;i++) { for(int j = 0;j<=rest;j++) { dp[i][j] = -1; } } return f1(N,E,rest,cur,dp); } //记忆化搜索 剪枝,已经计算过的直接从缓存种取 时间复杂度:O(N*rest) public static int f1(int N,int E,int rest,int cur,int[][] dp) { if(dp[cur][rest] != -1) { return dp[cur][rest]; } if(rest == 0) { dp[cur][rest] = cur == E ? 1 : 0; return dp[cur][rest]; } if(cur == 1) { dp[cur][rest] = f1(N,E,rest - 1,2); }else if(cur == N) { dp[cur][rest] = f1(N,E,rest - 1,N - 1); }else { dp[cur][rest] = f1(N,E,rest - 1,cur + 1) + f1(N,E,rest - 1,cur - 1); } return dp[cur][rest]; } //严格表结构动态规划 时间复杂度于记忆化搜索相同 public static int dpWay(int N,int start,int end,int K) { int[][] dp = new int[K + 1][N+1]; dp[0][end] = 1; for(int rest = 1;rest <= K;rest++) { for(int cur = 1;cur <= N;cur++) { if(cur == 1) { dp[rest][cur] = dp[rest-1][2]; }else if(cur == N) { dp[rest][cur] = dp[rest-1][cur-1]; }else { dp[rest][cur] = dp[rest-1][cur+1] + dp[rest-1][cur-1]; } } } return dp[start][K]; } }
选硬币
给定一个硬币数组,求拼成target的最少硬币数
package com.wtp.基础提升.暴力递归改动态规划; public class 选硬币 { public static void main(String[] args) { int[] arr = {2,7,3,5,3,4}; System.out.println(minCount1(arr,11)); System.out.println(process(arr,11)); System.out.println(process(arr,11,0)); } public static int minCount(int[] arr,int target) { return process(arr,target,0); } public static int minCount1(int[] arr,int target) { int[][] dp = new int[arr.length+1][target+1]; for(int i = 0;i<=arr.length;i++) { for(int j = 0;j<=target;j++) { dp[i][j] = -2; } } int res = process(arr,target,0,dp); return res != -1 ? res : 0; } //暴力尝试 public static int process(int[] arr,int target,int i) { if(target == 0) {//有解返回0个硬币 return 0; } if(target < 0 || i == arr.length) { return -1; } int p1; int p2; p1 = process(arr,target - arr[i],i+1); p2 = process(arr,target,i+1); if(p1 == -1 && p2 == -1) { return -1; } int res = Integer.MAX_VALUE; res = p1 != -1 ? Math.min(res, p1 + 1) : res; res = p2 != -1 ? Math.min(res, p2) : res; return res; } //记忆化搜索 public static int process(int[] arr,int target,int index,int[][] dp) { if(target < 0) { return -1; } if(dp[index][target] != -2) { return dp[index][target]; } if(target == 0) {//有解返回0个硬币 dp[index][target] = 0; return 0; } if(index == arr.length) { dp[index][target] = -1; return -1; } int p1; int p2; p1 = process(arr,target - arr[index],index+1,dp); p2 = process(arr,target,index+1,dp); int res = Integer.MAX_VALUE; if(p1 == -1 && p2 == -1) { res = -1; }else { res = p1 != -1 ? Math.min(res, p1+1) : res; res = p2 != -1 ? Math.min(res, p2) : res; } dp[index][target] = res; return res; } //动态规划 public static int process(int[] arr,int aim) { int[][] dp = new int[arr.length + 1][aim + 1]; int N = arr.length; for (int row = 0; row <= N; row++) { dp[row][0] = 0; } for (int col = 1; col <= aim; col++) { dp[N][col] = -1; } for (int index = N - 1; index >= 0; index--) { for (int rest = 1; rest <= aim; rest++) { int p1 = dp[index + 1][rest];// 不拿 int p2 = -1;// 拿的后续 if (rest - arr[index] >= 0) { p2 = dp[index + 1][rest - arr[index]]; } if (p1 == -1 && p2 == -1) { dp[index][rest] = -1; } else if (p1 == -1) { dp[index][rest] = p2 + 1; } else if (p2 == -1) { dp[index][rest] = p1; } else { dp[index][rest] = Math.min(p1, p2 + 1); } } } return dp[0][N]; } }
两个绝顶聪明的人
两个绝顶聪明的人选牌,只能选数组两侧的牌,都选对自己最有利的,返回最后获胜的人的值
package com.wtp.基础提升.暴力递归改动态规划; public class 两个绝顶聪明的人选牌 { public static void main(String[] args) { int[] arr = {3,100,2,50}; System.out.println(process1(arr)); System.out.println(process2(arr)); System.out.println(process3(arr)); } public static int process1(int[] arr) { if(arr == null || arr.length == 0) { return 0; } return Math.max(f(arr,0,arr.length - 1), p(arr,0,arr.length - 1)); } //范围尝试 先手函数 public static int f(int[] arr,int l,int r) { if(l == r) { return arr[r]; } return Math.max(arr[l] + p(arr,l+1,r), arr[r] + p(arr,l,r-1)); } //范围尝试 后手函数 public static int p(int[] arr,int l,int r) { if(l == r) { return 0; } return Math.min(f(arr,l+1,r), f(arr,l,r-1)); } //记忆化搜索 public static int process2(int[] arr) { if(arr == null || arr.length == 0) { return 0; } int N = arr.length ; int[][] dp1 = new int[N + 1][N + 1]; int[][] dp2 = new int[N + 1][N + 1]; for(int i = 0; i <= N;i++) { for(int j = 0;j <= N;j++) { dp1[i][j] = -1; dp2[i][j] = -1; } } return Math.max(f2(arr,0,arr.length - 1,dp1), p2(arr,0,arr.length - 1,dp2)); } public static int f2(int[] arr,int l,int r,int[][] dp) { if(dp[l][r] != -1) { return dp[l][r]; } if(l == r) { dp[l][r] = arr[r]; return arr[r]; } dp[l][r] = Math.max(arr[l] + p2(arr,l+1,r,dp), arr[r] + p2(arr,l,r-1,dp)); return dp[l][r]; } public static int p2(int[] arr,int l,int r,int[][] dp) { if(dp[l][r] != -1) { return dp[l][r]; } if(l == r) { dp[l][r] = 0; return 0; } dp[l][r] = Math.min(f2(arr,l+1,r,dp), f2(arr,l,r-1,dp)); return dp[l][r]; } //动态规划 public static int process3(int[] arr) { int N = arr.length; int[][] dp1 = new int[N + 1][N + 1]; int[][] dp2 = new int[N + 1][N + 1]; for(int i = 0;i < N;i++) { dp1[i][i] = arr[i]; dp2[i][i] = 0; } int row = 0; int col = 1; while(col < N) { int i = row; int j = col; while(i < N && j < N) { dp1[i][j] = Math.max(dp2[i+1][j] + arr[i],dp2[i][j-1] + arr[j]); dp2[i][j] = Math.min(dp1[i+1][j],dp1[i][j-1]); i++;j++; } col++; } return Math.max(dp1[0][N-1], dp2[0][N-1]); } }
棋盘马跳位置
一张9*10的棋盘,马初始在 0,0 跳 k 步跳到 i,j 有多少种方法
package com.wtp.基础提升.暴力递归改动态规划; public class 棋盘马跳位置 { public static void main(String[] args) { // System.out.println(process1(7,5,10)); //System.out.println(process3(7,5,10)); test(); } public static void test() { int maxCount = 1000; int i; int j; int k; long start = 0L; long l1 = 0L; long l2 = 0L; long l3 = 0L; long end = 0L; for(int z = 0;z < maxCount;z++) { i = (int)(Math.random() * 9); j = (int)(Math.random() * 10); k = (int)(Math.random() * 10); //System.out.println(i + "==" + j + "==" + k + "=="); start = System.currentTimeMillis(); int res1 = process1(i,j,k); end = System.currentTimeMillis(); l1 += end - start; start = System.currentTimeMillis(); int res2 = process2(i,j,k); end = System.currentTimeMillis(); l2 += end - start; start = System.currentTimeMillis(); process3(i,j,k); end = System.currentTimeMillis(); l3 += end - start; if(res1 != res2) { System.out.println("error!"); return; } } System.out.println("暴力递归用时:" + l1 + "毫秒"); System.out.println("记忆化搜索用时:" + l2 + "毫秒"); System.out.println("动态规划用时:" + l3 + "毫秒"); } //9*10的棋盘 public static int process1(int i,int j,int k) { if(i < 0 || i > 8 || j < 0 || j > 9) { return 0; } if(k == 0) { return i == 0 && j == 0 ? 1 : 0; } return process1(i + 1,j + 2,k-1) +process1(i + 1,j - 2,k-1) +process1(i - 1,j + 2,k-1) +process1(i - 1,j - 2,k-1) +process1(i + 2,j + 1,k-1) +process1(i + 2,j - 1,k-1) +process1(i - 2,j + 1,k-1) +process1(i - 2,j - 1,k-1); } public static int process2(int i,int j,int k) { int[][][] dp = new int[9][10][k+1]; for(int x = 0;x < 9;x++) { for(int y = 0;y < 10;y++) { for(int z = 0;z <= k;z++) { dp[x][y][z] = -1; } } } return process(i,j,k,dp); } public static int process(int i,int j,int k,int[][][] dp) { if(i < 0 || i > 8 || j < 0 || j > 9) { return 0; } if(dp[i][j][k] != -1) { return dp[i][j][k]; } if(k == 0) { dp[i][j][k] = i == 0 && j == 0 ? 1 : 0; return dp[i][j][k]; } dp[i][j][k] = process(i + 1,j + 2,k-1,dp) +process(i + 1,j - 2,k-1,dp) +process(i - 1,j + 2,k-1,dp) +process(i - 1,j - 2,k-1,dp) +process(i + 2,j + 1,k-1,dp) +process(i + 2,j - 1,k-1,dp) +process(i - 2,j + 1,k-1,dp) +process(i - 2,j - 1,k-1,dp); return dp[i][j][k]; } public static int process3(int i,int j,int k) { int[][][] dp = new int[9][10][k+1]; dp[0][0][0] = 1; int[] xx = {1,1,-1,-1,2,2,-2,-2}; int[] yy = {2,-2,2,-2,1,-1,1,-1}; for(int z = 1;z <= k;z++) {//有多少层 x*y的平面 for(int x = 0;x < 9;x++) { for(int y = 0;y < 10;y++) { for(int n = 0;n < xx.length;n++) { int x1 = x + xx[n]; int y1 = y + yy[n]; if(x1 > -1 && x1 < 9 && y1 > - 1 && y1 < 10 && z -1 >= 0) { dp[x][y][z] += dp[x1][y1][z-1]; } } } } } return dp[i][j][k]; } }
鲍勃走格子
给定长宽N、M的矩阵,越界死亡,求等概率随机走k步最后的存活率
package com.wtp.基础提升.暴力递归改动态规划; public class 鲍勃走格子 { public static void main(String[] args) { // int[] res = process1(3,4,5,5,3); // System.out.println(res[0] + "/" + res[1] + "=" + res[0] * 1.0 / res[1]); // // System.out.println(bob1(5,5,3,4,3)); // System.out.println(bob2(5,5,3,4,3)); // System.out.println(bob3(5,5,3,4,3)); test(); } public static void test() { int maxCount = 10000; int i; int j; int k; int N; int M; long start = 0L; long l1 = 0L; long l2 = 0L; long l3 = 0L; long end = 0L; for(int z = 0;z < maxCount;z++) { N = (int)(Math.random() * 10) + 1; M = (int)(Math.random() * 10) + 1; i = N / 2; j = M / 2; k = (int)(Math.random() * 10); //System.out.println(i + "==" + j + "==" + k + "=="); start = System.currentTimeMillis(); String res1 = bob1(N,M,i,j,k); end = System.currentTimeMillis(); l1 += end - start; start = System.currentTimeMillis(); String res2 = bob2(N,M,i,j,k); end = System.currentTimeMillis(); l2 += end - start; start = System.currentTimeMillis(); String res3 = bob3(N,M,i,j,k); end = System.currentTimeMillis(); l3 += end - start; if(!res1.equals(res2)) { System.out.println("error!"); System.out.println(res1); System.out.println(res2); System.out.println(res3); return; } } System.out.println("暴力递归用时:" + l1 + "毫秒"); System.out.println("记忆化搜索用时:" + l2 + "毫秒"); System.out.println("动态规划用时:" + l3 + "毫秒"); } public static String bob1(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 gcd(long m, long n) {//辗转相除法求最大公因数 return n == 0 ? m : gcd(n, m % n); } //死了还能走 总共有4^rest走法 public static long process(int N,int M,int row,int col,int rest) {//存活数 if(row < 0 || row == N || col < 0 || col == M) { return 0;//出界了 } if(rest == 0) { return 1;//没出界 走完了 存活 } long live = process(N, M, row - 1, col, rest - 1); live += process(N, M, row + 1, col, rest - 1); live += process(N, M, row, col - 1, rest - 1); live += process(N, M, row, col + 1, rest - 1); return live; } //死了就不走了 public static int[] process1(int i,int j,int N,int M,int k) { if(i < 0 || i >= N || j < 0 || j >= M) { return new int[] {0,1};//走一次死了 } if(k == 0) { return new int[] {1,1};//走一次活了 } int[] res = new int[2]; int[] xx = {1,-1,0,0}; int[] yy = {0,0,1,-1}; for(int n = 0;n < xx.length;n++) { int[] p = process1(i+xx[n],j+yy[n],N,M,k-1); res[0] += p[0]; res[1] += p[1]; } return res; } public static String bob2(int N,int M,int i,int j,int k) { long all = (long)Math.pow(4, k); long live = process2(N, M, i, j, k); long gcd = gcd(all,live); return String.valueOf((live / gcd) + "/" + (all / gcd)); } public static long process2(int N,int M,int i,int j,int k) { long[][][] dp = new long[N][M][k+1]; for(int x = 0;x < N;x++) { for(int y = 0;y < M;y++) { for(int z = 0;z <= k;z++) { dp[x][y][z] = -1; } } } return process2(N,M,i,j,k,dp); } //记忆化搜索 public static long process2(int N,int M,int row,int col,int rest,long[][][] dp) {//存活数 if(row < 0 || row >= N || col < 0 || col >= M) { return 0;//出界了 } if(dp[row][col][rest] != -1) { return dp[row][col][rest]; } if(rest == 0) { dp[row][col][rest] = 1; return 1;//没出界 走完了 存活 } long live = process2(N, M, row - 1, col, rest - 1,dp); live += process2(N, M, row + 1, col, rest - 1,dp); live += process2(N, M, row, col - 1, rest - 1,dp); live += process2(N, M, row, col + 1, rest - 1,dp); dp[row][col][rest] = live; return live; } public static String bob3(int N,int M,int i,int j,int k) { long all = (long)Math.pow(4, k); long live = process3(N, M, i, j, k); long gcd = gcd(all,live); return String.valueOf((live / gcd) + "/" + (all / gcd)); } //动态规划 public static long process3(int N,int M,int row,int col,int rest) { long[][][] dp = new long[N][M][rest + 1]; for(int i = 0;i < N;i++) {//步数为0的时候在范围上就都存活 for(int j = 0;j < M;j++) { dp[i][j][0] = 1; } } int[] xx = {1,-1,0,0}; int[] yy = {0,0,1,-1}; for(int z = 1;z <= rest;z++) { for(int i = 0;i < N;i++) { for(int j = 0;j < M;j++) { for(int n = 0;n < xx.length;n++) { int x1 = xx[n] + i; int y1 = yy[n] + j; if(x1 >= 0 && x1 < N && y1 >= 0 && y1 < M) { dp[i][j][z] += dp[x1][y1][z-1]; } } } } } return dp[row][col][rest]; } }
选货币每种可以选无限张
优化: 在存在枚举行为时,也许可以通过斜率优化,观察枚举行为是否能被邻近位置所替代。
必须有尝试(从左到右或者范围上的尝试) -> 记忆化搜索 -> 严格表结构动态规划 -> 优化
尝试:可变参数个数越少越好、单可变参数维度保持0维
package com.wtp.基础提升.暴力递归改动态规划; import java.util.Arrays; public class 选货币每种可以选无限张 { public static void main(String[] args) { test(); } public static void test() { int maxCount = 1000; int maxLength = 10; int maxValue = 20; int rest; long start = 0L; long end = 0L; long l1 = 0L; long l2 = 0L; long l3 = 0L; long l4 = 0L; for(int z = 0;z < maxCount;z++) { int[] arr1 = getRandomArr(maxLength,maxValue); int[] arr2 = getCopyArr(arr1); int[] arr3 = getCopyArr(arr1); rest = (int)(Math.random() * 100) + 1; start = System.currentTimeMillis(); int res1 = process1(arr1,rest); end = System.currentTimeMillis(); l1 += end - start; start = System.currentTimeMillis(); int res2 = process2(arr2,rest); end = System.currentTimeMillis(); l2 += end - start; start = System.currentTimeMillis(); int res3 = process3(arr3,rest); end = System.currentTimeMillis(); l3 += end - start; start = System.currentTimeMillis(); int res4 = process4(arr3,rest); end = System.currentTimeMillis(); l4 += end - start; if(res1 != res2 || res2 != res3 || res3 != res4) { System.out.println("error!"); System.out.println(res1); System.out.println(res2); System.out.println(res3); System.out.println(Arrays.toString(arr1)); System.out.println(rest); return; } } System.out.println("暴力递归用时:" + l1 + "毫秒"); System.out.println("记忆化搜索用时:" + l2 + "毫秒"); System.out.println("动态规划用时:" + l3 + "毫秒"); System.out.println("动态规划(优化)用时:" + l3 + "毫秒"); } public static int[] getCopyArr(int[] arr) { int[] copyArr = new int[arr.length]; System.arraycopy(arr, 0, copyArr, 0, arr.length); return copyArr; } public static int[] getRandomArr(int maxLength,int maxValue) { int[] arr = new int[maxLength]; for(int i = 0;i < arr.length;i++) { arr[i] = (int)(Math.random() * maxValue) + 1; } return arr; } //尝试 public static int process1(int[] arr,int rest) { return process1(arr,0,rest); } public static int process1(int[] arr, int index, int rest) { if (index == arr.length) { return rest == 0 ? 1 : 0; } int r = 0; for (int zhang = 0; arr[index] * zhang <= rest; zhang++) { r += process1(arr, index + 1, rest - arr[index] * zhang); } return r; } //记忆化搜索 public static int process2(int[] arr,int rest) { int N = arr.length; int[][] dp = new int[N + 1][rest+1]; for(int i = 0;i <= N;i++) { for(int j = 0;j <= rest;j++) { dp[i][j] = -1; } } return process2(arr,0,rest,dp); } public static int process2(int[] arr,int index,int rest,int[][] dp) { if (index == arr.length) { dp[index][rest] = rest == 0 ? 1 : 0; return dp[index][rest]; } if(dp[index][rest] != -1) { return dp[index][rest]; } int r = 0; for (int zhang = 0; arr[index] * zhang <= rest; zhang++) { r += process2(arr, index + 1, rest - arr[index] * zhang,dp); } dp[index][rest] = r; return dp[index][rest]; } //动态规划 0 rest public static int process3(int[] arr,int rest) { int N = arr.length; int[][] dp = new int[N+1][rest+1]; dp[N][0] = 1; for(int i = N - 1;i >= 0;i--) { for(int j = 0;j <= rest;j++) { for(int k = 0;k * arr[i] <= j;k++) { dp[i][j] += dp[i+1][j - arr[i] * k]; } } } return dp[0][rest]; } //动态规划斜率优化 public static int process4(int[] arr,int rest) { int N = arr.length; int[][] dp = new int[N+1][rest+1]; dp[N][0] = 1; for(int i = N - 1;i >= 0;i--) { for(int j = 0;j <= rest;j++) { dp[i][j] = dp[i+1][j];//每个格子首先依赖自己下方的格子 if(j - arr[i] >= 0) { dp[i][j] += dp[i][j-arr[i]];//其次依赖自己行左边的格子(左边已经先算完自己下面所有依赖的格子了) } } } return dp[0][rest]; } }