【算法笔记】汇总——贪心篇
本篇内容的主旨在于总结LeetCode
中常见的贪心题涉及的基本内容,并对此做出一定的总结与归纳,算是笔者心路历程的一些许感悟。
首先,我们将贪心题按难易程度划分为如下情况:
贪心简单题
以下三道题目就是简单题,大家会发现贪心感觉就是常识。是的,如下三道题目,就是靠常识,分析出局部最优是什么,全局最优是什么,贪心贪的也就有理有据!
贪心算法:分发饼干
贪心算法:K次取反后最大化的数组和
贪心算法:柠檬水找零
贪心中等题
贪心中等题,靠常识可能就有点想不出来了。开始初现贪心算法的难度与巧妙之处。
贪心算法:摆动序列
贪心算法:单调递增的数字
public int monotoneIncreasingDigits(int n) {
if (n == 0) {
return 0;
}
char[] arr = Integer.toString(n).toCharArray();
int start = Integer.MAX_VALUE;
for (int i = arr.length - 1; i >= 1; i--) {
if (arr[i - 1] > arr[i]) {
start = i;
arr[i - 1]--;
}
}
StringBuilder res = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
if (arr[i] == '0' && i == 0) {
continue;
}
if (i >= start) {
res.append('9');
} else {
res.append(arr[i]);
}
}
return Integer.parseInt(res.toString());
}
贪心解决股票问题
大家都知道股票系列问题是动规的专长,其实用贪心也可以解决,而且还不止就这两道题目,但这两道比较典型,我就拿来单独说一说
贪心算法:买卖股票的最佳时机II
贪心算法:买卖股票的最佳时机含手续费
两个维度权衡问题
在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。
贪心算法:分发糖果
public int candy(int[] ratings) {
int n = ratings.length;
int[] candyCnt = new int[n];
Arrays.fill(candyCnt, 1);
for (int i = 1; i < n; i++) {
if (ratings[i] > ratings[i - 1]) {
candyCnt[i] = candyCnt[i - 1] + 1;
}
}
for (int i = n - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]) {
candyCnt[i] = Math.max(candyCnt[i], candyCnt[i + 1] + 1);
}
}
return Arrays.stream(candyCnt).sum();
}
贪心算法:根据身高重建队列
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, (a, b) -> {
if (a[0] == b[0]) {
return a[1] - b[1];
}
return b[0] - a[0];
});
LinkedList<int[]> queue = new LinkedList<>();
for (int[] p : people) {
queue.add(p[1], p);
}
return queue.toArray(new int[people.length][]);
}
在讲解本题的过程中,还强调了编程语言的重要性,模拟插队的时候,使用list(链表)替代了vector(动态数组),效率会高很多。
大家也要掌握自己所用的编程语言,理解其内部实现机制,这样才能写出高效的算法!
贪心难题
这里的题目如果没有接触过,其实是很难想到的,甚至接触过,也一时想不出来,所以题目不要做一遍,要多练!
贪心解决区间问题
关于区间问题,大家应该印象深刻,有一周我们专门讲解的区间问题,各种覆盖各种去重。
贪心算法:跳跃游戏
贪心算法:跳跃游戏II
贪心算法:用最少数量的箭引爆气球
public int findMinArrowShots(int[][] points) {
if (points.length == 0) {
return 0;
}
Arrays.sort(points, (a, b) -> {
return Integer.compare(a[0], b[0]);
});
int res = 1;
for (int i = 1; i < points.length; i++) {
if (points[i - 1][1] < points[i][0]) {
res++;
} else {
points[i][1] = Math.min(points[i][1], points[i - 1][1]);
}
}
return res;
}
贪心算法:无重叠区间
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length < 2) {
return 0;
}
Arrays.sort(intervals, (a, b) -> {
if (a[1] != b[1]) {
return Integer.compare(a[1], b[1]);
} else {
return Integer.compare(a[0], b[0]);
}
});
int res = 1, end = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= end) {
res++;
end = intervals[i][1];
}
}
return intervals.length - res;
}
贪心算法:划分字母区间
贪心算法:合并区间
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> {
return a[0] - b[0];
});
ArrayList<int[]> res = new ArrayList<>();
res.add(intervals[0]);
int last = intervals[0][1];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= last) {
int[] tmp = res.get(res.size() - 1);
tmp[1] = Math.max(tmp[1], intervals[i][1]);
last = tmp[1];
} else {
res.add(intervals[i]);
last = intervals[i][1];
}
}
return res.toArray(new int[res.size()][]);
}
其他难题
贪心算法:最大子序和 其实是动态规划的题目,但贪心性能更优,很多同学也是第一次发现贪心能比动规更优的题目。
贪心算法:加油站可能以为是一道模拟题,但就算模拟其实也不简单,需要把while用的很娴熟。但其实是可以使用贪心给时间复杂度降低一个数量级。
public int canCompleteCircuit(int[] gas, int[] cost) {
int curSum = 0, start = 0, totalSum = 0;
for (int i = 0; i < gas.length; i++) {
curSum += gas[i] - cost[i];
totalSum += gas[i] - cost[i];
if (curSum < 0) {
start = i + 1;
curSum = 0;
}
}
if (totalSum < 0) {
return -1;
}
return start;
}
最后贪心系列压轴题目贪心算法:我要监控二叉树,不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,这就是典型的交叉类难题了。
int cnt = 0;
public int minCameraCover(TreeNode root) {
if (trval(root) == 0) {
cnt++;
}
return cnt;
}
/**
* 0:无覆盖,1:摄像头,2:有覆盖
*
* @param root
* @return
*/
private int trval(TreeNode root) {
if (root == null) {
return 2;
}
int left = trval(root.left);
int right = trval(root.right);
if (left == 2 && right == 2) {
return 0;
}
if (left == 0 || right == 0) {
cnt++;
return 1;
}
if (left == 1 || right == 1) {
return 2;
}
return 0;
}