• 【算法训练营】 - ①② 从暴力递归到动态规划


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

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

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

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

    面试题和动态规划的关系

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

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

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

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

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

    知道了面试中设计暴力递归过程的原则,然后呢?

    一定要逼自己找到不违反原则情况下的暴力尝试!
    如果你找到的暴力尝试,不符合原则,马上舍弃!找新的!
    如果某个题目突破了设计原则,一定极难极难,面试中出现概率低于5%!

    常见的4种尝试模型

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

    如何分析有没有重复解

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

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

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

    动态规划的进一步优化

    1. 空间压缩
    2. 状态化简
    3. 四边形不等式
      其他优化技巧略

    什么是动态规划?

    记忆化搜索(最糙的动态规划):暴力递归中有重复计算,然后我们缓存住计算出来的东西,下次遇见相同的直接拿值。

    机器人走路

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

    package class18;
    
    public class Code01_RobotWalk {
    
    	public static int ways1(int N, int start, int aim, int K) {
    		if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) {
    			return -1;
    		}
    		return process1(start, K, aim, N);
    	}
    
    	// 机器人当前来到的位置是cur,
    	// 机器人还有rest步需要去走,
    	// 最终的目标是aim,
    	// 有哪些位置?1~N
    	// 返回:机器人从cur出发,走过rest步之后,最终停在aim的方法数,是多少?
    	public static int process1(int cur, int rest, int aim, int N) {
    		if (rest == 0) { // 如果已经不需要走了,走完了!
    			return cur == aim ? 1 : 0;
    		}
    		// (cur, rest)
    		if (cur == 1) { // 1 -> 2
    			return process1(2, rest - 1, aim, N);
    		}
    		// (cur, rest)
    		if (cur == N) { // N-1 <- N
    			return process1(N - 1, rest - 1, aim, N);
    		}
    		// (cur, rest)
    		return process1(cur - 1, rest - 1, aim, N) + process1(cur + 1, rest - 1, aim, N);
    	}
    
    	public static int ways2(int N, int start, int aim, int K) {
    		if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) {
    			return -1;
    		}
    		int[][] dp = new int[N + 1][K + 1];
    		for (int i = 0; i <= N; i++) {
    			for (int j = 0; j <= K; j++) {
    				dp[i][j] = -1;
    			}
    		}
    		// dp就是缓存表
    		// dp[cur][rest] == -1 -> process1(cur, rest)之前没算过!
    		// dp[cur][rest] != -1 -> process1(cur, rest)之前算过!返回值,dp[cur][rest]
    		// N+1 * K+1
    		return process2(start, K, aim, N, dp);
    	}
    
    	// cur 范: 1 ~ N
    	// rest 范:0 ~ K
    	public static int process2(int cur, int rest, int aim, int N, int[][] dp) {
    		if (dp[cur][rest] != -1) {
    			return dp[cur][rest];
    		}
    		// 之前没算过!
    		int ans = 0;
    		if (rest == 0) {
    			ans = cur == aim ? 1 : 0;
    		} else if (cur == 1) {
    			ans = process2(2, rest - 1, aim, N, dp);
    		} else if (cur == N) {
    			ans = process2(N - 1, rest - 1, aim, N, dp);
    		} else {
    			ans = process2(cur - 1, rest - 1, aim, N, dp) + process2(cur + 1, rest - 1, aim, N, dp);
    		}
    		dp[cur][rest] = ans;
    		return ans;
    
    	}
    
    	public static int ways3(int N, int start, int aim, int K) {
    		if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) {
    			return -1;
    		}
    		int[][] dp = new int[N + 1][K + 1];
    		dp[aim][0] = 1;
    		for (int rest = 1; rest <= K; rest++) {
    			dp[1][rest] = dp[2][rest - 1];
    			for (int cur = 2; cur < N; cur++) {
    				dp[cur][rest] = dp[cur - 1][rest - 1] + dp[cur + 1][rest - 1];
    			}
    			dp[N][rest] = dp[N - 1][rest - 1];
    		}
    		return dp[start][K];
    	}
    
    	public static void main(String[] args) {
    		System.out.println(ways1(5, 2, 4, 6));
    		System.out.println(ways2(5, 2, 4, 6));
    		System.out.println(ways3(5, 2, 4, 6));
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    货币组合方法

    定数组arr,arr中所有的值都为正数且不重复每个值代表一种面值的货币,每种面值的货币可以使用任意张再给定一个整数 aim,代表要找的钱数求组成 aim 的方法数。求组成 aim 的方法数?

    package class21;
    
    public class Code03_CoinsWayNoLimit {
    
        public static int coinsWay(int[] arr, int aim) {
            if (arr == null || arr.length == 0 || aim < 0) {
                return 0;
            }
            return process(arr, 0, aim);
        }
    
        // arr[index....] 所有的面值,每一个面值都可以任意选择张数,组成正好rest这么多钱,方法数多少?
        public static int process(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 += process(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];
            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);
        }
    
        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 dp1(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;
            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 dp2(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;
            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 int[] randomArray(int maxLen, int maxValue) {
            int N = (int) (Math.random() * maxLen);
            int[] arr = new int[N];
            boolean[] has = new boolean[maxValue + 1];
            for (int i = 0; i < N; i++) {
                do {
                    arr[i] = (int) (Math.random() * maxValue) + 1;
                } while (has[arr[i]]);
                has[arr[i]] = true;
            }
            return arr;
        }
    
        // 为了测试
        public static void printArray(int[] arr) {
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println();
        }
    
        // 为了测试
        public static void main(String[] args) {
            int maxLen = 10;
            int maxValue = 30;
            int testTime = 1000000;
            System.out.println("测试开始");
            for (int i = 0; i < testTime; i++) {
                int[] arr = randomArray(maxLen, maxValue);
                int aim = (int) (Math.random() * maxValue);
                int ans1 = coinsWay(arr, aim);
                int ans2 = dp1(arr, aim);
                int ans3 = dp2(arr, aim);
                if (ans1 != ans2 || ans1 != ans3) {
                    System.out.println("Oops!");
                    printArray(arr);
                    System.out.println(aim);
                    System.out.println(ans1);
                    System.out.println(ans2);
                    System.out.println(ans3);
                    break;
                }
            }
            System.out.println("测试结束");
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138

    多样本位置全对应的尝试模型

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

    package class19;
    
    // 这个问题leetcode上可以直接测
    // 链接:https://leetcode.com/problems/longest-common-subsequence/
    public class Code04_LongestCommonSubsequence {
    
    	public static int longestCommonSubsequence1(String s1, String s2) {
    		if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
    			return 0;
    		}
    		char[] str1 = s1.toCharArray();
    		char[] str2 = s2.toCharArray();
    		// 尝试
    		return process1(str1, str2, str1.length - 1, str2.length - 1);
    	}
    
    	// str1[0...i]和str2[0...j],这个范围上最长公共子序列长度是多少?
    	// 可能性分类:
    	// a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾
    	// b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾
    	// c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾
    	// d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾
    	// 注意:a)、b)、c)、d)并不是完全互斥的,他们可能会有重叠的情况
    	// 但是可以肯定,答案不会超过这四种可能性的范围
    	// 那么我们分别来看一下,这几种可能性怎么调用后续的递归。
    	// a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾
    	//    如果是这种情况,那么有没有str1[i]和str2[j]就根本不重要了,因为这两个字符一定没用啊
    	//    所以砍掉这两个字符,最长公共子序列 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归)
    	// b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾
    	//    如果是这种情况,那么我们可以确定str2[j]一定没有用,要砍掉;但是str1[i]可能有用,所以要保留
    	//    所以,最长公共子序列 = str1[0...i]与str2[0...j-1]的最长公共子序列长度(后续递归)
    	// c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾
    	//    跟上面分析过程类似,最长公共子序列 = str1[0...i-1]与str2[0...j]的最长公共子序列长度(后续递归)
    	// d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾
    	//    同时可以看到,可能性d)存在的条件,一定是在str1[i] == str2[j]的情况下,才成立的
        //    所以,最长公共子序列总长度 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归) + 1(共同的结尾)
    	// 综上,四种情况已经穷尽了所有可能性。四种情况中取最大即可
    	// 其中b)、c)一定参与最大值的比较,
    	// 当str1[i] == str2[j]时,a)一定比d)小,所以d)参与
    	// 当str1[i] != str2[j]时,d)压根不存在,所以a)参与
    	// 但是再次注意了!
    	// a)是:str1[0...i-1]与str2[0...j-1]的最长公共子序列长度
    	// b)是:str1[0...i]与str2[0...j-1]的最长公共子序列长度
    	// c)是:str1[0...i-1]与str2[0...j]的最长公共子序列长度
    	// a)中str1的范围 < b)中str1的范围,a)中str2的范围 == b)中str2的范围
    	// 所以a)不用求也知道,它比不过b)啊,因为有一个样本的范围比b)小啊!
    	// a)中str1的范围 == c)中str1的范围,a)中str2的范围 < c)中str2的范围
    	// 所以a)不用求也知道,它比不过c)啊,因为有一个样本的范围比c)小啊!
    	// 至此,可以知道,a)就是个垃圾,有它没它,都不影响最大值的决策
    	// 所以,当str1[i] == str2[j]时,b)、c)、d)中选出最大值
    	// 当str1[i] != str2[j]时,b)、c)中选出最大值
    	public static int process1(char[] str1, char[] str2, int i, int j) {
    		if (i == 0 && j == 0) {
    			// str1[0..0]和str2[0..0],都只剩一个字符了
    			// 那如果字符相等,公共子序列长度就是1,不相等就是0
    			// 这显而易见
    			return str1[i] == str2[j] ? 1 : 0;
    		} else if (i == 0) {
    			// 这里的情况为:
    			// str1[0...0]和str2[0...j],str1只剩1个字符了,但是str2不只一个字符
    			// 因为str1只剩一个字符了,所以str1[0...0]和str2[0...j]公共子序列最多长度为1
    			// 如果str1[0] == str2[j],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了
    			// 如果str1[0] != str2[j],只是此时不相等而已,
    			// 那么str2[0...j-1]上有没有字符等于str1[0]呢?不知道,所以递归继续找
    			if (str1[i] == str2[j]) {
    				return 1;
    			} else {
    				return process1(str1, str2, i, j - 1);
    			}
    		} else if (j == 0) {
    			// 和上面的else if同理
    			// str1[0...i]和str2[0...0],str2只剩1个字符了,但是str1不只一个字符
    			// 因为str2只剩一个字符了,所以str1[0...i]和str2[0...0]公共子序列最多长度为1
    			// 如果str1[i] == str2[0],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了
    			// 如果str1[i] != str2[0],只是此时不相等而已,
    			// 那么str1[0...i-1]上有没有字符等于str2[0]呢?不知道,所以递归继续找
    			if (str1[i] == str2[j]) {
    				return 1;
    			} else {
    				return process1(str1, str2, i - 1, j);
    			}
    		} else { // i != 0 && j != 0
    			// 这里的情况为:
    			// str1[0...i]和str2[0...i],str1和str2都不只一个字符
    			// 看函数开始之前的注释部分
    			// p1就是可能性c)
    			int p1 = process1(str1, str2, i - 1, j);
    			// p2就是可能性b)
    			int p2 = process1(str1, str2, i, j - 1);
    			// p3就是可能性d),如果可能性d)存在,即str1[i] == str2[j],那么p3就求出来,参与pk
    			// 如果可能性d)不存在,即str1[i] != str2[j],那么让p3等于0,然后去参与pk,反正不影响
    			int p3 = str1[i] == str2[j] ? (1 + process1(str1, str2, i - 1, j - 1)) : 0;
    			return Math.max(p1, Math.max(p2, p3));
    		}
    	}
    
    	public static int longestCommonSubsequence2(String s1, String s2) {
    		if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
    			return 0;
    		}
    		char[] str1 = s1.toCharArray();
    		char[] str2 = s2.toCharArray();
    		int N = str1.length;
    		int M = str2.length;
    		int[][] dp = new int[N][M];
    		dp[0][0] = str1[0] == str2[0] ? 1 : 0;
    		for (int j = 1; j < M; j++) {
    			dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1];
    		}
    		for (int i = 1; i < N; i++) {
    			dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0];
    		}
    		for (int i = 1; i < N; i++) {
    			for (int j = 1; j < M; j++) {
    				int p1 = dp[i - 1][j];
    				int p2 = dp[i][j - 1];
    				int p3 = str1[i] == str2[j] ? (1 + dp[i - 1][j - 1]) : 0;
    				dp[i][j] = Math.max(p1, Math.max(p2, p3));
    			}
    		}
    		return dp[N - 1][M - 1];
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125

    寻找业务限制的尝试模型

    原味咖啡杯

    package class20;
    
    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.PriorityQueue;
    
    // 题目
    // 数组arr代表每一个咖啡机冲一杯咖啡的时间,每个咖啡机只能串行的制造咖啡。
    // 现在有n个人需要喝咖啡,只能用咖啡机来制造咖啡。
    // 认为每个人喝咖啡的时间非常短,冲好的时间即是喝完的时间。
    // 每个人喝完之后咖啡杯可以选择洗或者自然挥发干净,只有一台洗咖啡杯的机器,只能串行的洗咖啡杯。
    // 洗杯子的机器洗完一个杯子时间为a,任何一个杯子自然挥发干净的时间为b。
    // 四个参数:arr, n, a, b
    // 假设时间点从0开始,返回所有人喝完咖啡并洗完咖啡杯的全部过程结束后,至少来到什么时间点。
    public class Code03_Coffee {
    
    	// 验证的方法
    	// 彻底的暴力
    	// 很慢但是绝对正确
    	public static int right(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 minTime1(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 bestTime(drinks, a, b, 0, 0);
    	}
    
    	// drinks 所有杯子可以开始洗的时间
    	// wash 单杯洗干净的时间(串行)
    	// air 挥发干净的时间(并行)
    	// free 洗的机器什么时候可用
    	// drinks[index.....]都变干净,最早的结束时间(返回)
    	public static int bestTime(int[] drinks, int wash, int air, int index, int free) {
    		if (index == drinks.length) {
    			return 0;
    		}
    		// index号杯子 决定洗
    		int selfClean1 = Math.max(drinks[index], free) + wash;
    		int restClean1 = bestTime(drinks, wash, air, index + 1, selfClean1);
    		int p1 = Math.max(selfClean1, restClean1);
    
    		// index号杯子 决定挥发
    		int selfClean2 = drinks[index] + air;
    		int restClean2 = bestTime(drinks, wash, air, index + 1, free);
    		int p2 = Math.max(selfClean2, restClean2);
    		return Math.min(p1, p2);
    	}
    
    	// 贪心+优良尝试改成动态规划
    	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 bestTimeDp(drinks, a, b);
    	}
    
    	public static int bestTimeDp(int[] drinks, int wash, int air) {
    		int N = drinks.length;
    		int maxFree = 0;
    		for (int i = 0; i < drinks.length; i++) {
    			maxFree = Math.max(maxFree, drinks[i]) + wash;
    		}
    		int[][] dp = new int[N + 1][maxFree + 1];
    		for (int index = N - 1; index >= 0; index--) {
    			for (int free = 0; free <= maxFree; free++) {
    				int selfClean1 = Math.max(drinks[index], free) + wash;
    				if (selfClean1 > maxFree) {
    					break; // 因为后面的也都不用填了
    				}
    				// index号杯子 决定洗
    				int restClean1 = dp[index + 1][selfClean1];
    				int p1 = Math.max(selfClean1, restClean1);
    				// index号杯子 决定挥发
    				int selfClean2 = drinks[index] + air;
    				int restClean2 = dp[index + 1][free];
    				int p2 = Math.max(selfClean2, restClean2);
    				dp[index][free] = Math.min(p1, p2);
    			}
    		}
    		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 len = 10;
    		int max = 10;
    		int testTime = 10;
    		System.out.println("测试开始");
    		for (int i = 0; i < testTime; i++) {
    			int[] arr = randomArray(len, max);
    			int n = (int) (Math.random() * 7) + 1;
    			int a = (int) (Math.random() * 7) + 1;
    			int b = (int) (Math.random() * 10) + 1;
    			int ans1 = right(arr, n, a, b);
    			int ans2 = minTime1(arr, n, a, b);
    			int ans3 = minTime2(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;
    			}
    		}
    		System.out.println("测试结束");
    
    	}
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
  • 相关阅读:
    ALP300智能型低压马达保护器
    完成flex布局与float布局
    Java项目:企业OA管理系统(java+SSM+HTML+LayUI+bootstrap+mysql)
    【STM32】STM32学习笔记-WDG看门狗(46)
    在Linux中进行GO语言安装
    CV每日论文--2024.6.26
    如何在Python中操作MySQL?
    XCTF---MISC---Misc文件类型
    Python - 通过/SSH 获取远程主机的 env 变量
    【补题日记】[2022杭电暑期多校3]K-Taxi
  • 原文地址:https://blog.csdn.net/weixin_42274953/article/details/126214072