算法基础12

算法基础12

一、什么暴力递归可以继续优化?

有重复调用同一个子问题的解,这种递归可以优化
如果每一个子问题都是不同的解,无法优化也不用优化

二、题目

1.题目1

假设有排成一行的N个位置,记为1~N,N一定大于或等于2
开始时机器人在其中的M位置(M一定是1~N中的一个)
如果机器人来到1位置,那么下一步只能往右来到2位置;
如果机器人来到N位置,那么下一步只能往左来到N-1位置;
如果机器人来到中间位置,那么下一步可以往左走或者往右走;
规定机器人必须走K步,最终能来到P位置(P也是1~N中的一个)的方法有多少种
给定四个参数N、M、K、P,返回方法数

package class12;

public class Code01_RobotWalk {

	public static int ways1(int N, int M, int K, int P) {
		// 参数无效直接返回0
		if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
			return 0;
		}
		// 总共N个位置,从M点出发,还剩K步,返回最终能达到P的方法数
		return walk(N, M, K, P);
	}

	// N : 位置为1 ~ N,固定参数
	// cur : 当前在cur位置,可变参数
	// rest : 还剩res步没有走,可变参数
	// P : 最终目标位置是P,固定参数
	// 该函数的含义:只能在1~N这些位置上移动,当前在cur位置,走完rest步之后,停在P位置的方法数作为返回值返回
	public static int walk(int N, int cur, int rest, int P) {
		// 如果没有剩余步数了,当前的cur位置就是最后的位置
		// 如果最后的位置停在P上,那么之前做的移动是有效的
		// 如果最后的位置没在P上,那么之前做的移动是无效的
		if (rest == 0) {
			return cur == P ? 1 : 0;
		}
		// 如果还有rest步要走,而当前的cur位置在1位置上,那么当前这步只能从1走向2
		// 后续的过程就是,来到2位置上,还剩rest-1步要走
		if (cur == 1) {
			return walk(N, 2, rest - 1, P);
		}
		// 如果还有rest步要走,而当前的cur位置在N位置上,那么当前这步只能从N走向N-1
		// 后续的过程就是,来到N-1位置上,还剩rest-1步要走
		if (cur == N) {
			return walk(N, N - 1, rest - 1, P);
		}
		// 如果还有rest步要走,而当前的cur位置在中间位置上,那么当前这步可以走向左,也可以走向右
		// 走向左之后,后续的过程就是,来到cur-1位置上,还剩rest-1步要走
		// 走向右之后,后续的过程就是,来到cur+1位置上,还剩rest-1步要走
		// 走向左、走向右是截然不同的方法,所以总方法数要都算上
		return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P);
	}
	
	
	
	
	
	
	public static int waysCache(int N, int M, int K, int P) {
		// 参数无效直接返回0
		if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
			return 0;
		}
		
		int[][] dp = new int[N+1][K+1];
		for(int row = 0; row <= N; row++) {
			for(int col = 0; col <= K; col++) {
				dp[row][col] = -1;
			}
		}
		return walkCache(N, M, K, P,dp);
	}

	// HashMap<String, Integer>   (19,100)  "19_100"
	// 我想把所有cur和rest的组合,返回的结果,加入到缓存里
	public static int walkCache(int N, int cur, int rest, int P, int[][] dp) {
		if(dp[cur][rest] != -1) {
			return dp[cur][rest];
		}
		if (rest == 0) {
			dp[cur][rest] = cur == P ? 1 : 0;
			return dp[cur][rest];
		}
		if (cur == 1) {
			dp[cur][rest] = walkCache(N, 2, rest - 1, P, dp);
			return dp[cur][rest];
		}
		if (cur == N) {
			dp[cur][rest] =walkCache(N, N - 1, rest - 1, P,dp);
			return dp[cur][rest];
		}
		dp[cur][rest] = walkCache(N, cur + 1, rest - 1, P,dp) 
				+ walkCache(N, cur - 1, rest - 1, P, dp);
		return dp[cur][rest];
	}
	
	
	
	
	
	
	
	
	

	public static int ways2(int N, int M, int K, int P) {
		// 参数无效直接返回0
		if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
			return 0;
		}
		int[][] dp = new int[K + 1][N + 1];
		dp[0][P] = 1;
		for (int i = 1; i <= K; i++) {
			for (int j = 1; j <= N; j++) {
				if (j == 1) {
					dp[i][j] = dp[i - 1][2];
				} else if (j == N) {
					dp[i][j] = dp[i - 1][N - 1];
				} else {
					dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
				}
			}
		}
		return dp[K][M];
	}

	public static int ways3(int N, int M, int K, int P) {
		// 参数无效直接返回0
		if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
			return 0;
		}
		int[] dp = new int[N + 1];
		dp[P] = 1;
		for (int i = 1; i <= K; i++) {
			int leftUp = dp[1];// 左上角的值
			for (int j = 1; j <= N; j++) {
				int tmp = dp[j];
				if (j == 1) {
					dp[j] = dp[j + 1];
				} else if (j == N) {
					dp[j] = leftUp;
				} else {
					dp[j] = leftUp + dp[j + 1];
				}
				leftUp = tmp;
			}
		}
		return dp[M];
	}

	// ways4是你的方法
	public static int ways4(int N, int M, int K, int P) {
		if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
			return 0;
		}
		return process(N, 0, P, M, K);
	}

	// 一共N个位置,从M点出发,一共只有K步。返回走到位置j,剩余步数为i的方法数
	public static int process(int N, int i, int j, int M, int K) {
		if (i == K) {
			return j == M ? 1 : 0;
		}
		if (j == 1) {
			return process(N, i + 1, j + 1, M, K);
		}
		if (j == N) {
			return process(N, i + 1, j - 1, M, K);
		}
		return process(N, i + 1, j + 1, M, K) + process(N, i + 1, j - 1, M, K);
	}

	// ways5是你的方法的dp优化
	public static int ways5(int N, int M, int K, int P) {
		if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
			return 0;
		}
		int[][] dp = new int[K + 1][N + 1];
		dp[K][M] = 1;
		for (int i = K - 1; i >= 0; i--) {
			for (int j = 1; j <= N; j++) {
				if (j == 1) {
					dp[i][j] = dp[i + 1][j + 1];
				} else if (j == N) {
					dp[i][j] = dp[i + 1][j - 1];
				} else {
					dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j - 1];
				}
			}
		}
		return dp[0][P];
	}

	public static void main(String[] args) {
		System.out.println(ways1(7, 4, 9, 5));
		System.out.println(ways2(7, 4, 9, 5));
		System.out.println(ways3(7, 4, 9, 5));
		System.out.println(ways4(7, 4, 9, 5));
		System.out.println(ways5(7, 4, 9, 5));
	}

}

2.背包问题

package class12;

public class Code03_Knapsack {

	public static int getMaxValue(int[] w, int[] v, int bag) {
		return process(w, v, 0, 0, bag);
	}

	// index... 最大价值
	public static int process(int[] w, int[] v, int index, int alreadyW, int bag) {
		if (alreadyW > bag) {
			return -1;
		}
		// 重量没超
		if (index == w.length) {
			return 0;
		}
		int p1 = process(w, v, index + 1, alreadyW, bag);
		int p2next = process(w, v, index + 1, alreadyW + w[index], bag);
		int p2 = -1;
		if (p2next != -1) {
			p2 = v[index] + p2next;
		}
		return Math.max(p1, p2);

	}

	public static int maxValue(int[] w, int[] v, int bag) {
		return process(w, v, 0, bag);
	}

	// 只剩下rest的空间了,
	// index...货物自由选择,但是不要超过rest的空间
	// 返回能够获得的最大价值
	public static int process(int[] w, int[] v, int index, int rest) {
		if (rest < 0) { // base case 1
			return -1;
		}
		// rest >=0
		if (index == w.length) { // base case 2
			return 0;
		}
		// 有货也有空间
		int p1 = process(w, v, index + 1, rest);
		int p2 = -1;
		int p2Next = process(w, v, index + 1, rest - w[index]);
		if(p2Next!=-1) {
			p2 = v[index] + p2Next;
		}
		return Math.max(p1, p2);
	}

	public static int dpWay(int[] w, int[] v, int bag) {
		int N = w.length;
		int[][] dp = new int[N + 1][bag + 1];
		// dp[N][...] = 0
		for (int index = N - 1; index >= 0; index--) {
			for (int rest = 0; rest <= bag; rest++) { // rest < 0
				int p1 = dp[index+1][rest];
				int p2 = -1;
				if(rest - w[index] >= 0) {
					p2 = v[index] + dp[index + 1][rest - w[index]];
				}
				dp[index][rest] = Math.max(p1, p2);
			}
		}
		return dp[0][bag];
	}

	public static void main(String[] args) {
		int[] weights = { 3, 2, 4, 7 };
		int[] values = { 5, 6, 3, 19 };
		int bag = 11;
		System.out.println(maxValue(weights, values, bag));
		System.out.println(dpWay(weights, values, bag));
	}

}

3.卡片问题

package class12;

public class Code04_CardsInLine {

	public static int win1(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) {
			return 0;
		}
		return Math.min(f(arr, L + 1, R), f(arr, L, R - 1));
	}

	public static int windp(int[] arr) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		int N = arr.length;
		int[][] f = new int[N][N];
		int[][] s = new int[N][N];
		for (int i = 0; i < N; i++) {
			f[i][i] = arr[i];
		}
		// 0,0 右下方移动
		// 0,1
		// 0,2
		// 0,N-1
		for (int col = 1; col < N; col++) {
			// 对角线出发位置(0,col)
			int L = 0;
			int R = col;
			while (L < N && R < N) {
				f[L][R] = Math.max(arr[L] + s[L + 1][R], arr[R] + s[L][R - 1]);
				s[L][R] = Math.min(f[L + 1][R], f[L][R - 1]);
				L++;
				R++;
			}
		}
		return Math.max(f[0][N - 1], s[0][N - 1]);
	}

	public static void main(String[] args) {
		int[] arr = { 5, 7, 4, 5, 8, 1, 6, 0, 3, 4, 6, 1, 7 };
		System.out.println(win1(arr));
		System.out.println(windp(arr));

	}

}

4.硬币问题

package class12;

public class Code09_CoinsWay {

	// arr中都是正数且无重复值,返回组成aim的方法数
	public static int ways1(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		return process1(arr, 0, aim);
	}

	public static int process1(int[] arr, int index, int rest) {
		if(index == arr.length) {
			return rest == 0 ? 1 : 0 ;	
		}
		int ways = 0;
		for(int zhang = 0;  zhang * arr[index] <= rest ;zhang++) {
			ways += process1(arr, index + 1, rest -  (zhang * arr[index])  );
		}
		return ways;
	}

	public static int ways2(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		int[][] dp = new int[arr.length+1][aim+1];
		// 一开始所有的过程,都没有计算呢
		// dp[..][..]  = -1
		for(int i = 0 ; i < dp.length; i++) {
			for(int j = 0 ; j < dp[0].length; j++) {
				dp[i][j] = -1;
			}
		}
		return process2(arr, 0, aim , dp);
	}
	
	// 如果index和rest的参数组合,是没算过的,dp[index][rest] == -1
	// 如果index和rest的参数组合,是算过的,dp[index][rest]  > - 1
	public static int process2(int[] arr, 
			int index, int rest,
			int[][] dp) {
		if(dp[index][rest] != -1) {
			return dp[index][rest];
		}
		if(index == arr.length) {
			dp[index][rest] = rest == 0 ? 1 :0;
			return  dp[index][rest];	
		}
		int ways = 0;
		for(int zhang = 0;  zhang * arr[index] <= rest ;zhang++) {
			ways += process2(arr, index + 1, rest -  (zhang * arr[index]) , dp );
		}
		dp[index][rest] = ways;
		return ways;
	}
	
	public static int ways3(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		int N = arr.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 1;// dp[N][1...aim] = 0;
		for(int index = N - 1; index >= 0; index--) {
			for(int rest = 0; rest <= aim; rest++) {
				int ways = 0;
				for(int zhang = 0;  zhang * arr[index] <= rest ;zhang++) {
					ways += dp[index + 1] [rest -  (zhang * arr[index])];
				}
				dp[index][rest] = ways;
			}
		}
		return dp[0][aim];
	}
	
	public static int ways4(int[] arr, int aim) {
		if (arr == null || arr.length == 0 || aim < 0) {
			return 0;
		}
		int N = arr.length;
		int[][] dp = new int[N + 1][aim + 1];
		dp[N][0] = 1;// dp[N][1...aim] = 0;
		for(int index = N - 1; index >= 0; index--) {
			for(int rest = 0; rest <= aim; rest++) {
				dp[index][rest] = dp[index+1][rest];
				if(rest - arr[index] >= 0) {
					dp[index][rest] += dp[index][rest - arr[index]];
				}
			}
		}
		return dp[0][aim];
	}
	

	public static void main(String[] args) {
		int[] arr = { 5, 10,50,100 };
		int sum = 1000;
		System.out.println(ways1(arr, sum));
		System.out.println(ways2(arr, sum));
		System.out.println(ways3(arr, sum));
		System.out.println(ways4(arr, sum));
	}

}

5.题目2

给定一个字符串str,给定一个字符串类型的数组arr。
arr里的每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来。
返回需要至少多少张贴纸可以完成这个任务。
例子:str=“babac”,arr = {“ba”,“c”,“abcd”}
至少需要两张贴纸"ba"和"abcd",因为使用这两张贴纸,把每一个字符单独剪开,含有2个a、2个b、1个c。是可以拼出str的。所以返回2。

package class12;

import java.util.Arrays;
import java.util.HashMap;

public class Code02_StickersToSpellWord {

	public static int minStickers1(String[] stickers, String target) {
		
		int n = stickers.length;
		
		int[][] map = new int[n][26];// stickers -> [26] [26] [26]
		for (int i = 0; i < n; i++) {
			char[] str = stickers[i].toCharArray();
			for (char c : str) {
				map[i][c - 'a']++;
			}
		}
		HashMap<String, Integer> dp = new HashMap<>();
		dp.put("", 0);
		return process1(dp, map, target);
	}

	// dp 傻缓存,如果t已经算过了,直接返回dp中的值
	// t 剩余的目标
	// 0..N每一个字符串所含字符的词频统计
	// 返回值是-1,map 中的贴纸  怎么都无法rest
	public static int process1(
			HashMap<String, Integer> dp,
			int[][] map, 
			String rest) {
		if (dp.containsKey(rest)) {
			return dp.get(rest);
		}
		// 以下就是正式的递归调用过程
		int ans = Integer.MAX_VALUE; // ans -> 搞定rest,使用的最少的贴纸数量 
		int n = map.length; // N种贴纸
		int[] tmap = new int[26]; // tmap 去替代 rest
		char[] target = rest.toCharArray();
		for (char c : target) {
			tmap[c - 'a']++;
		}
		for (int i = 0; i < n; i++) {
			// 枚举当前第一张贴纸是谁?
			if (map[i][target[0] - 'a'] == 0) {
				continue;
			}
			StringBuilder sb = new StringBuilder();
			// i 贴纸, j 枚举a~z字符
			for (int j = 0; j < 26; j++) { // 
				if (tmap[j] > 0) { // j这个字符是target需要的
					for (int k = 0; k < Math.max(0, tmap[j] - map[i][j]); k++) {
						sb.append((char) ('a' + j));
					}
				}
			}
			// sb ->  i
			String s = sb.toString();
			int tmp = process1(dp, map, s);
			if (tmp != -1) {
				ans = Math.min(ans, 1 + tmp);
			}
		}
		// ans 系统最大  rest
		dp.put(rest, ans == Integer.MAX_VALUE ? -1 : ans);
		return dp.get(rest);
	}

	public static int minStickers2(String[] stickers, String target) {
		int n = stickers.length;
		int[][] map = new int[n][26];
		for (int i = 0; i < n; i++) {
			char[] str = stickers[i].toCharArray();
			for (char c : str) {
				map[i][c - 'a']++;
			}
		}
		char[] str = target.toCharArray();
		int[] tmap = new int[26];
		for (char c : str) {
			tmap[c - 'a']++;
		}
		HashMap<String, Integer> dp = new HashMap<>();
		int ans = process2(map, 0, tmap, dp);
		return ans;
	}

	public static int process2(int[][] map, int i, int[] tmap, HashMap<String, Integer> dp) {
		StringBuilder keyBuilder = new StringBuilder();
		keyBuilder.append(i + "_");
		for (int asc = 0; asc < 26; asc++) {
			if (tmap[asc] != 0) {
				keyBuilder.append((char) (asc + 'a') + "_" + tmap[asc] + "_");
			}
		}
		String key = keyBuilder.toString();
		if (dp.containsKey(key)) {
			return dp.get(key);
		}
		boolean finish = true;
		for (int asc = 0; asc < 26; asc++) {
			if (tmap[asc] != 0) {
				finish = false;
				break;
			}
		}
		if (finish) {
			dp.put(key, 0);
			return 0;
		}
		if (i == map.length) {
			dp.put(key, -1);
			return -1;
		}
		int maxZhang = 0;
		for (int asc = 0; asc < 26; asc++) {
			if (map[i][asc] != 0 && tmap[asc] != 0) {
				maxZhang = Math.max(maxZhang, (tmap[asc] / map[i][asc]) + (tmap[asc] % map[i][asc] == 0 ? 0 : 1));
			}
		}
		int[] backup = Arrays.copyOf(tmap, tmap.length);
		int min = Integer.MAX_VALUE;
		int next = process2(map, i + 1, tmap, dp);
		tmap = Arrays.copyOf(backup, backup.length);
		if (next != -1) {
			min = next;
		}
		for (int zhang = 1; zhang <= maxZhang; zhang++) {
			for (int asc = 0; asc < 26; asc++) {
				tmap[asc] = Math.max(0, tmap[asc] - (map[i][asc] * zhang));
			}
			next = process2(map, i + 1, tmap, dp);
			tmap = Arrays.copyOf(backup, backup.length);
			if (next != -1) {
				min = Math.min(min, zhang + next);
			}
		}
		int ans = min == Integer.MAX_VALUE ? -1 : min;
		dp.put(key, ans);
		return ans;
	}
	
	public static void main(String[] args) {
		String[] arr = {"aaaa","bbaa","ccddd"};
		String str = "abcccccdddddbbbaaaaa";
		System.out.println(minStickers1(arr, str));
		System.out.println(minStickers2(arr, str));
		
		
	}

}

6.题目3

两个字符串的最长公共子序列问题

package class12;

public class Code05_PalindromeSubsequence {

	public static int lcse(char[] str1, char[] str2) {
		
		
		
		
		int[][] dp = new int[str1.length][str2.length];
		
		
		
		dp[0][0] = str1[0] == str2[0] ? 1 : 0;
		
		
		for (int i = 1; i < str1.length; i++) {
			dp[i][0] = Math.max(dp[i - 1][0], str1[i] == str2[0] ? 1 : 0);
		}
		for (int j = 1; j < str2.length; j++) {
			dp[0][j] = Math.max(dp[0][j - 1], str1[0] == str2[j] ? 1 : 0);
		}
		for (int i = 1; i < str1.length; i++) {
			for (int j = 1; j < str2.length; j++) {
				dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
				if (str1[i] == str2[j]) {
					dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
				} 
			}
		}
		return dp[str1.length - 1][str2.length - 1];
	}

	public static void main(String[] args) {

	}

}

7. 题目4

给定一个数组,代表每个人喝完咖啡准备刷杯子的时间
只有一台咖啡机,一次只能洗一个杯子,时间耗费a,洗完才能洗下一杯
每个咖啡杯也可以自己挥发干净,时间耗费b,咖啡杯可以并行挥发
返回让所有咖啡杯变干净的最早完成时间
三个参数:int [] arr、int a、int b

package class12;

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

// 题目
// 数组arr代表每一个咖啡机冲一杯咖啡的时间,每个咖啡机只能串行的制造咖啡。
// 现在有n个人需要喝咖啡,只能用咖啡机来制造咖啡。
// 认为每个人喝咖啡的时间非常短,冲好的时间即是喝完的时间。
// 每个人喝完之后咖啡杯可以选择洗或者自然挥发干净,只有一台洗咖啡杯的机器,只能串行的洗咖啡杯。
// 洗杯子的机器洗完一个杯子时间为a,任何一个杯子自然挥发干净的时间为b。
// 四个参数:arr, n, a, b
// 假设时间点从0开始,返回所有人喝完咖啡并洗完咖啡杯的全部过程结束后,至少来到什么时间点。
public class Code06_Coffee {

	// 方法一:暴力尝试方法
	public static int minTime1(int[] arr, int n, int a, int b) {
		int[] times = new int[arr.length];
		int[] drink = new int[n];
		return forceMake(arr, times, 0, drink, n, a, b);
	}

	// 方法一,每个人暴力尝试用每一个咖啡机给自己做咖啡
	public static int forceMake(int[] arr, int[] times, int kth, int[] drink, int n, int a, int b) {
		if (kth == n) {
			int[] drinkSorted = Arrays.copyOf(drink, kth);
			Arrays.sort(drinkSorted);
			return forceWash(drinkSorted, a, b, 0, 0, 0);
		}
		int time = Integer.MAX_VALUE;
		for (int i = 0; i < arr.length; i++) {
			int work = arr[i];
			int pre = times[i];
			drink[kth] = pre + work;
			times[i] = pre + work;
			time = Math.min(time, forceMake(arr, times, kth + 1, drink, n, a, b));
			drink[kth] = 0;
			times[i] = pre;
		}
		return time;
	}

	// 方法一,暴力尝试洗咖啡杯的方式
	public static int forceWash(int[] drinks, int a, int b, int index, int washLine, int time) {
		if (index == drinks.length) {
			return time;
		}
		// 选择一:当前index号咖啡杯,选择用洗咖啡机刷干净
		int wash = Math.max(drinks[index], washLine) + a;
		int ans1 = forceWash(drinks, a, b, index + 1, wash, Math.max(wash, time));

		// 选择二:当前index号咖啡杯,选择自然挥发
		int dry = drinks[index] + b;
		int ans2 = forceWash(drinks, a, b, index + 1, washLine, Math.max(dry, time));
		return Math.min(ans1, ans2);
	}

	// 方法二:稍微好一点的解法
	public static class Machine {
		public int timePoint;
		public int workTime;

		public Machine(int t, int w) {
			timePoint = t;
			workTime = w;
		}
	}

	public static class MachineComparator implements Comparator<Machine> {

		@Override
		public int compare(Machine o1, Machine o2) {
			return (o1.timePoint + o1.workTime) - (o2.timePoint + o2.workTime);
		}

	}

	// 方法二,每个人暴力尝试用每一个咖啡机给自己做咖啡,优化成贪心
	public static int minTime2(int[] arr, int n, int a, int b) {
		PriorityQueue<Machine> heap = new PriorityQueue<Machine>(new MachineComparator());
		for (int i = 0; i < arr.length; i++) {
			heap.add(new Machine(0, arr[i]));
		}
		int[] drinks = new int[n];
		for (int i = 0; i < n; i++) {
			Machine cur = heap.poll();
			cur.timePoint += cur.workTime;
			drinks[i] = cur.timePoint;
			heap.add(cur);
		}
		return process(drinks, a, b, 0, 0);
	}

	// 方法二,洗咖啡杯的方式和原来一样,只是这个暴力版本减少了一个可变参数

	// process(drinks, 3, 10, 0,0)
	// a 洗一杯的时间 固定变量
	// b 自己挥发干净的时间 固定变量
	// drinks 每一个员工喝完的时间 固定变量
	// drinks[0..index-1]都已经干净了,不用你操心了
	// drinks[index...]都想变干净,这是我操心的,washLine表示洗的机器何时可用
	// drinks[index...]变干净,最少的时间点返回
	public static int process(int[] drinks, int a, int b, int index, int washLine) {
		if (index == drinks.length - 1) {
			return Math.min(Math.max(washLine, drinks[index]) + a, drinks[index] + b);
		}
		// 剩不止一杯咖啡
		// wash是我当前的咖啡杯,洗完的时间
		int wash = Math.max(washLine, drinks[index]) + a;// 洗,index一杯,结束的时间点
		// index+1...变干净的最早时间
		int next1 = process(drinks, a, b, index + 1, wash);
		// index....
		int p1 = Math.max(wash, next1);
		int dry = drinks[index] + b; // 挥发,index一杯,结束的时间点
		int next2 = process(drinks, a, b, index + 1, washLine);
		int p2 = Math.max(dry, next2);
		return Math.min(p1, p2);
	}

	public static int dp(int[] drinks, int a, int b) {
		if (a >= b) {
			return drinks[drinks.length - 1] + b;
		}
		// a < b
		int N = drinks.length;
		int limit = 0; // 咖啡机什么时候可用
		for (int i = 0; i < N; i++) {
			limit = Math.max(limit, drinks[i]) + a;
		}
		int[][] dp = new int[N][limit + 1];
		// N-1行,所有的值
		for (int washLine = 0; washLine <= limit; washLine++) {
			dp[N - 1][washLine] = Math.min(Math.max(washLine, drinks[N - 1]) + a, drinks[N - 1] + b);
		}
		for (int index = N - 2; index >= 0; index--) {
			for (int washLine = 0; washLine <= limit; washLine++) {
				int p1 = Integer.MAX_VALUE;
				int wash = Math.max(washLine, drinks[index]) + a;
				if (wash <= limit) {
					p1 = Math.max(wash, dp[index + 1][wash]);
				}
				int p2 = Math.max(drinks[index] + b, dp[index + 1][washLine]);
				dp[index][washLine] = Math.min(p1, p2);
			}
		}
		return dp[0][0];
	}

	// 方法三:最终版本,把方法二洗咖啡杯的暴力尝试进一步优化成动态规划
	public static int minTime3(int[] arr, int n, int a, int b) {
		PriorityQueue<Machine> heap = new PriorityQueue<Machine>(new MachineComparator());
		for (int i = 0; i < arr.length; i++) {
			heap.add(new Machine(0, arr[i]));
		}
		int[] drinks = new int[n];
		for (int i = 0; i < n; i++) {
			Machine cur = heap.poll();
			cur.timePoint += cur.workTime;
			drinks[i] = cur.timePoint;
			heap.add(cur);
		}
		if (a >= b) {
			return drinks[n - 1] + b;
		}
		int[][] dp = new int[n][drinks[n - 1] + n * a];
		for (int i = 0; i < dp[0].length; i++) {
			dp[n - 1][i] = Math.min(Math.max(i, drinks[n - 1]) + a, drinks[n - 1] + b);
		}
		for (int row = n - 2; row >= 0; row--) { // row 咖啡杯的编号
			int washLine = drinks[row] + (row + 1) * a;
			for (int col = 0; col < washLine; col++) {
				int wash = Math.max(col, drinks[row]) + a;
				dp[row][col] = Math.min(Math.max(wash, dp[row + 1][wash]), Math.max(drinks[row] + b, dp[row + 1][col]));
			}
		}
		return dp[0][0];
	}

	// for test
	public static int[] randomArray(int len, int max) {
		int[] arr = new int[len];
		for (int i = 0; i < len; i++) {
			arr[i] = (int) (Math.random() * max) + 1;
		}
		return arr;
	}

	// for test
	public static void printArray(int[] arr) {
		System.out.print("arr : ");
		for (int j = 0; j < arr.length; j++) {
			System.out.print(arr[j] + ", ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int[] test = { 1, 1, 5, 5, 7, 10, 12, 12, 12, 12, 12, 12, 15 };
		int a1 = 3;
		int b1 = 10;
		System.out.println(process(test, a1, b1, 0, 0));
		System.out.println(dp(test, a1, b1));

		int len = 5;
		int max = 9;
		int testTime = 50000;
		for (int i = 0; i < testTime; i++) {
			int[] arr = randomArray(len, max);
			int n = (int) (Math.random() * 5) + 1;
			int a = (int) (Math.random() * 5) + 1;
			int b = (int) (Math.random() * 10) + 1;
			int ans1 = minTime1(arr, n, a, b);
			int ans2 = minTime2(arr, n, a, b);
			int ans3 = minTime3(arr, n, a, b);
			if (ans1 != ans2 || ans2 != ans3) {
				printArray(arr);
				System.out.println("n : " + n);
				System.out.println("a : " + a);
				System.out.println("b : " + b);
				System.out.println(ans1 + " , " + ans2 + " , " + ans3);
				System.out.println("===============");
				break;
			}
		}

	}

}

三、暴力递归和动态规划的关系

某一个暴力递归,有解的重复调用,就可以把这个暴力递归优化成动态规划
任何动态规划问题,都一定对应着某一个有解的重复调用的暴力递归
但不是所有的暴力递归,都一定对应着动态规划

四、面试题和动态规划的关系

解决一个问题,可能有很多尝试方法
可能在很多尝试方法中,又有若干个方法有动态规划的方式
一个问题,可能有若干种动态规划的解法

五、如何找到某个问题的动态规划方式?

1)设计暴力递归:设计原则+4种常见尝试模型!重点!
2)分析有没有重复解:套路解决
3)用记忆化搜索->用严格表结构实现动态规划;套路解决
4)看看能否继续优化:套路解决

六、面试中设计暴力递归过程的原则

1)每一个可变参数的类型,一定不要比int类型更加复杂
2)原则1)可以违反,让类型突破到一维线性结构,那必须是唯一可变参数
3)如果发现原则1)被违反,但不违反原则2),只需要做到记忆化搜索即可
4)可变参数的个数,能少则少

七、常见的4种尝试模型

1)从左往右的尝试模型
2)范围上的尝试模型
3)多样本位置全对应的尝试模型
4)寻找业务限制的尝试模型

八、如何分析有没有重复解

列出调用过程,可以只列出前几层
有没有重复解,一看便知

九、暴力递归到动态规划的套路

1)你已经有了一个不违反原则的暴力递归,而且的确存在解的重复调用
2)找到哪些参数的变化会影响返回值,对每一个列出变化范围
3)参数间的所有的组合数量,意味着表大小
4)记忆化搜索的方法就是傻缓存,非常容易得到
5)规定好严格表的大小,分析位置的依赖顺序,然后从基础填写到最终解
6)对于有枚举行为的决策过程,进一步优化

十、动态规划的进一步优化

1)空间压缩
2)状态化简
3)四边形不等式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值