明天再讲一篇对于利用贪婪算法解决的题目。
一、金条切割问题
1、题目形容
一块金条切成两半,是须要破费和长度数值一样的铜板的。比方长度为20的金条,不管怎么切,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?输出一个数组,返回宰割的最小代价。
例如:
给定数组{10,20,30},代表一共三个人, 整块金条长度为10 + 20 + 30 = 60,金条要分成10, 20, 30三个局部(不思考程序)。
如果先把长度60的金条分成10和50,破费60;再把长度50的金条分成20和30,破费50;一共破费110铜板。
但如果先把长度60的金条分成30和30,破费60;再把长度30金条分成10和20,破费30;一共破费90铜板。
2、思路
(1)筹备一个小根堆。将数组放到这个小根堆里。
(2)每次弹出堆顶的两个数求和为A,将A再放回小根堆里。
(3)始终执行第2步,直到堆只剩一个数。最初,每一次第二步A的累加和即是最初的后果。
例如给定的金条长度为150,要分成10、20、30、40、50的块,最初破费的铜板数量即是上图中蓝色圆圈的和,即150+60+90+30=330。
也就是咱们代码求解的时候是从叶子往根求的,求完后再从根往叶子即是金条的切割程序,最初所有的叶子即是须要切成的块的大小。
3、代码
/** * @author Java和算法学习:周一 */public static int lessMoneySplitGold(int[] arr) { if (arr == null || arr.length == 0) { return 0; } // 筹备一个小根堆 Queue<Integer> queue = new PriorityQueue<>(); // 将所有数放到小根堆中 for (Integer s : arr) { queue.offer(s); } int result = 0; int current; while (queue.size() > 1) { // 每次弹出堆顶两个数求和 current = queue.poll() + queue.poll(); result += current; queue.offer(current); } return result;}
蕴含对数器的所有代码地址:https://github.com/monday-pro/algorithm-study/blob/master/src/basic/greedy/LessMoneySplitGold.java
二、点灯问题
1、题目形容
给定一个字符串str,只由 'X' 和 '.' 两种字符形成。'X’ 示意墙,不能放灯,点亮不点亮都可;'.' 示意居民点,能够放灯,须要点亮。如果灯放在i地位,能够让 i-1,i 和 i+1 三个地位被点亮。返回如果点亮str中所有须要点亮的地位,至多须要几盏灯。
2、思路
(1)i 地位是 'X’,不论,来到 i + 1地位
(2)i 地位是 '.' ,i + 1是 'X’,i 地位须要放灯,来到 i + 2地位
(3)i 地位是 '.' ,i + 1是 '.',i + 2是 '.',i + 1 地位须要放灯,来到 i + 3地位(此步即是贪婪)
(4)i 地位是 '.' ,i + 1是 '.',i + 2是 'X’,i 或 i + 1 地位须要放灯,来到 i + 3地位
3、代码
/** * @author Java和算法学习:周一 */public static int light(String light) { char[] lightChars = light.toCharArray(); // 曾经点灯的数量 int result = 0; // 以后来到的地位 int i = 0; while (i < lightChars.length) { // i 地位是 'X’,不论,来到 i + 1地位 if (lightChars[i] == 'X') { i++; } else { // i 地位是 '.' ,不论后续是怎么的,都要点一个灯 result++; if (i + 1 == lightChars.length) { break; } else { // i 地位是 '.' ,i + 1是 'X’,i 地位须要放灯,来到 i + 2地位 if (lightChars[i + 1] == 'X') { i = i + 2; } else { // i 地位是 '.' ,i + 1是 '.',i + 2是 '.',i + 1 地位须要放灯,来到 i + 3地位 // i 地位是 '.' ,i + 1是 '.',i + 2是 'X’,i 或 i + 1 地位须要放灯,来到 i + 3地位 i = i + 3; } } } } return result;}
蕴含对数器的所有代码地址:https://github.com/monday-pro/algorithm-study/blob/master/src/basic/greedy/Light.java
三、IPO问题
1、题目形容
链接:https://leetcode-cn.com/probl...
假如 力扣(LeetCode)行将开始 IPO 。为了以更高的价格将股票卖给风险投资公司,力扣心愿在 IPO 之前发展一些我的项目以减少其资本。因为资源无限,它只能在 IPO 之前实现最多 k 个不同的我的项目(串行做我的项目)。帮忙力扣设计实现最多 k 个不同我的项目后失去最大总资本的形式。
给你 n 个我的项目。对于每个我的项目 i ,它都有一个纯利润 profits[i] ,和启动该我的项目须要的最小资本 capital[i] 。
最后,你的资本为 w 。当你实现一个我的项目时,你将取得纯利润,且利润将被增加到你的总资本中。
总而言之,从给定我的项目中抉择 最多 k 个不同我的项目的列表,以最大化最终资本 ,并输入最终可取得的最多资本。
示例:
输出:k = 2, w = 0, profits = [1,2,3], capital = [0,1,1]
输入:4
解释:
因为你的初始资本为 0,你仅能够从 0 号我的项目开始。
在实现后,你将取得 1 的利润,你的总资本将变为 1。
此时你能够抉择开始 1 号或 2 号我的项目。
因为你最多能够抉择两个我的项目,所以你须要实现 2 号我的项目以取得最大的资本。
因而,输入最初最大化的资本,为 0 + 1 + 3 = 4。
2、思路
(1)筹备一个小根堆,以启动我的项目须要的资本为规范,将所有我的项目放到小根堆里
(2)筹备一个大根堆,把此时能做的我的项目从小根堆弹出来(领有的资本 >= 我的项目启动资本),以利润为规范将我的项目放到大根堆中,弹出大根堆中的堆顶我的项目A,k加1,w加A的利润
(3)始终执行第2步,直到达到k个我的项目,最初返回w。
和游戏中的打怪降级很像,每次都在本人打得过的怪中找到收益最大的,始终打,本人越来越强同时保障不会被战胜。
3、代码
/** * @author Java和算法学习:周一 */public static int findMaximizedCapital(int k, int w, int[] profits, int[] capital) { // 筹备一个小根堆 Queue<Program> capitalSmallQueue = new PriorityQueue<>((a, b) -> a.capital - b.capital); // 以启动我的项目须要的资本为规范,将所有我的项目放到小根堆中 for (int i = 0; i < profits.length; i++) { capitalSmallQueue.offer(new Program(profits[i], capital[i])); } // 筹备一个大根堆 // 放入的我的项目按我的项目的利润为规范 Queue<Program> profitsBigQueue = new PriorityQueue<>((a, b) -> b.profit - a.profit); for (int i = 0; i < k; i++) { // 以后领有的资本 >= 我的项目的启动资本,将我的项目从小根堆中弹出放到大根堆中 while (!capitalSmallQueue.isEmpty() && w >= capitalSmallQueue.peek().capital) { profitsBigQueue.offer(capitalSmallQueue.poll()); } // 可能资本有余,造成当初还能做我的项目然而大根堆没有我的项目给你做了 if (profitsBigQueue.isEmpty()) { return w; } w += profitsBigQueue.poll().profit; } return w;}public static class Program { public int profit; public int capital; public Program(int profit, int capital) { this.profit = profit; this.capital = capital; }}
本题能够间接在LeetCode上测试,所以就不须要对数器来验证了。
4、测试后果
是不是发现贪婪算法的解法正如上一篇所说,难的在于贪婪策略的提出和证实,代码往往都比较简单,同时在面试时区分度也不是很高。