LeetCode刷题笔记——贪心算法一、贪心算法概述
贪心算法（又称贪婪算法）是指，在对问题求解时，总是做出在当前看来是最好的选择。也就是说，不从整体最优上加以考虑，算法得到的是在某种意义上的局部最优解，重点在于“局部”的选取
二、典型例题
2.1、去除重复字符串

要求字典序最小，我们就尽可能地取寻找这样的子串，使得每一个字符都出现过，并且尽可能按照“abcdefg……”这样的字典升序排列
拿“bcacb”来举例
第一个遍历到的字符是b，我们将其存入结果中
第二个字符是c，bc的结果是满足要求的，因此我们也将其存入结果中
第三个字符是a，如果存入了，结果是bca，ca显然不满足升序，得把c放到a后面才满足我们的需要，那么a后面有没有c呢？显然是有的，所以我们就把之前存储的c拿走，现在结果中存了ba，那么ba满足要求么？同样不满足，但是a后面还是有b可以选的，所以可以大胆的把当前的b也拿走，结果中再存入a，“a”这个结果就满足升序要求了
下一个字符是c，"ac"满足升序么？满足，所以往下走就行
下一个字符是b，“acb”中的“cb”不满足升序，但是b之后还有可以用的c么？没了，所以只能存进去了，至此整个字符串遍历完成，得到结果“acb”
经过上面这一番梳理其实已经可以看出来了，在本题的算法中，尽可能地使结果按照升序排列是“贪心”的体现，贪心的“局部”选取的是结果中相邻的两个字符，只要相邻两个字符之间两两满足升序，那么总体就是升序的，因此可以通过这一“局部”的最优解得到整体的最优解
再回到我们的题目本身，从分析中我们看到，对字符串的处理只需要遍历一次，但是遍历过程中有两个东西是必须知道的
第一，后面还有没有可以用的字母了；
第二，这个字母结果中有没有
因此我们需要对这两个变量进行单独的存储，这样一来，根据思路，解题的代码就可以给出了
class Solution { public String removeDuplicateLetters(String s) { //转为字符数组方便遍历 char[] arr = s.toCharArray(); int length = arr.length; //存储每个字母出现的最大末尾坐标 int[] lastIndex = new int[26]; for (int i = 0; i < length; i++) { lastIndex[arr[i] - 'a'] = i; } //存储结果 StringBuffer result = new StringBuffer(); //存储某一字母在结果中是否出现过 boolean[] used = new boolean[26]; //首字母是肯定会暂时存入结果中的 result.append(arr[0]); used[arr[0] - 'a'] = true; for (int i = 1; i < length; i++) { char ch = arr[i]; //字母没有被添加过才需要进行处理，添加过的直接跳过 if (!used[ch - 'a']) { //当前结果中的栈顶值索引 int index = result.length() - 1; //不满足升序就一致要判断 while (index >= 0 && result.charAt(index) > ch) { //栈顶值 char c = result.charAt(index); //不满足升序的情况下，如果后面有可以用的，出栈 if (lastIndex[c - 'a'] > i) { used[c - 'a'] = false; result.deleteCharAt(index); index = result.length() - 1; } else //这个break千万别忘了，否则就继续遍历 break; } //添加结果 result.append(ch); used[ch - 'a'] = true; } } return result.toString(); } }
2.2、移除k位数字

采用贪心算法，重点在于寻找局部的最优解，这道题其实和上一道去除重复字符串是一样的，去除重复字符串是尽可能按照字典升序选择，而这道题则是尽可能把小的数字放在最前面，因此寻找局部最优就可以视为放入结果集中一个数字，如果后面的数字比他小，就把前面的数字拿出来而把当前数字放进去，这样就能保证最高位都是尽可能小的数字，然而就像去除重复字符串的题目要保证每一个字母都出现过一样，这道题要保证必须去除了几个数字，不能多也不能少，所以结果每变更一次都要记得处理，下面给出具体的代码实现
class Solution { public String removeKdigits(String num, int k) { int length = num.length(); //补丁 if (k >= length) return "0"; //把去除的个数改为需要的个数 k = length - k; StringBuffer result = new StringBuffer(); //记录0是否要添加 for (int i = 0; i < length; i++) { char ch = num.charAt(i); int resultLength = result.length(); //还有备用数字才判断 if ((length - i) > k) { while (resultLength > 0 && result.charAt(resultLength - 1) > ch) { result.deleteCharAt(resultLength - 1); k++; resultLength = result.length(); //备用数字不够了就不用判断了 if ((length - i) == k) break; } } //不再需要数字了就不追加结果了,但是下一个数字还是要判断的，因此用continue别用break if (k == 0) continue; //需要追加结果把需要的数字减一 k--; //如果0前面有数字，就加上 if (ch == '0' && resultLength == 0) continue; result.append(ch); } String res = result.toString(); res = res.equals("") ? "0" : res; return res; } }
2.3、任务调度器
class Solution { public int leastInterval(char[] tasks, int n) { int length = tasks.length; //记录每一种任务出现的次数 int[] count = new int[26]; for (int i = 0; i < length; i++) { count[tasks[i] - 'A']++; } Arrays.sort(count); int maxCount = 0; //统计有多少个频率最高的字母 for (int i = 25; i >= 0; i--) { if(count[i] != count[25]){ break; } maxCount++; } //注意这里说的是最短时间而不是必须是这个时间 //缝隙没有全填满就是第一个值，全填满了就是第二个值 return Math.max((count[25] - 1) * (n + 1) + maxCount , length); } }