“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情”
贪心算法的思想
顾名思义,贪心就是看到什么东西就要得到,贪心算法的核心思想与此相似。贪心算法着眼于做出当前情况的最优解,而选择忽略了全局最优解,从而导致它并不能对所有问题都达到整体最优解,但是,用贪心算法的出来的解即使不是全局最优也是全局最优的近似。
与动态规划的异同
动态归划和贪心可以解的题目都具有最优子结构性质,但是动态规划在取完一个局部最优解后,对新的子问题求解时,需要根据上一个局部最优解来得出新的局部最优解,而贪心则不需要做"回顾"这一步。在解题范围方面,动态规划明显优于贪心,但是对于两者都能解的问题,贪心的效率要略优于动态规划
贪心算法的思想看起来简单,但要贯彻一直选择局部最优解也需要对问题有着深入的认识。下面来看看题目 盛最多水的容器
思路
看到这种题脑子里肯定会闪过暴力解决,因为题目好理解且看起来简单,但是有经验的人知道暴力肯定不行(别问,问就是头铁)。
那么,我们要怎么高效求出最优解呢?其实可以用这样的角度看问题:题目中提供了矩形(把题目中的容积这样理解)的长(两条线中较短一条的值)和宽(两条线之间的距离),既然长的值很难确定,那可不可以把宽拉到最大,然后慢慢收缩找出最优解呢?如果以这个想法下来,我们需要知道如何收缩才能找到最优解,下面开始分类讨论(假设两条线分别为i和j,i的初始位置为最左边,j的初始位置为最右边):
- 若i长度小于j,则若i向右移,则这时矩形的宽变小,长的变化未知,面积变化也未知
- (同上),若j向左移,则宽变小,长肯定不会变大,面积必然变小
- 若i长度大于j,则若i向右移,则这时矩形的宽变小,长的变化不会变大,面积变小
- (同上),若j向左移,则宽变小,长的变化未知,面积变化未知
- 若两者相同,则移动哪一边都可以
讨论完情况之后,代码实现自然简单多了
代码实现
class Solution {
public int maxArea(int[] height) {
int n=height.length;
int ans=0;
int left=0;
int right=n-1;
//双指针收缩遍历整个数组
while(left<right){
int top=Math.min(height[left],height[right]);
int now=top*(right-left);
if(now>ans)ans=now;//把最大值记录下来
if(height[left]==top){
left++;
}else{
right--;
}
}
return ans;
}
}
下面再来看一道题 用最少数量的箭引爆气球
思路
要用较少的箭去射中更多的气球,那我们就需要去找到能够一次射中多个气球的位置,也就是数组中所表示的线段的交集,显然寻找交集可以通过比较一条线段的右端另一条线段的左端的大小来得出此两条线段是否相交(这里有个前提:前者的左端必须比后者的右端要小),用这样的方式可以找出线段之间是否存在交集,那么是否可以只通过这样的方式去找出最优解呢?看起来不一定,但这肯定是局部(线段数量比较少的情况,比如说3条)最优解,符合贪心的思想。
既然如此,那我们就可以遍历整个points数组,判断哪些线段间存在交集,存在交集的线段直接用一支箭射过就行了。
代码实现
class Solution {
public int findMinArrowShots(int[][] points) {
if(points.length==0)return 0;
Arrays.sort(points, Comparator.comparingInt(o -> o[1]));//以线段的右端值大小作为比较基准进行排序
int count=1;
int arrow1=points[0][1];
//这里为什么要使用右端作为箭射出的位置?我们不妨这样假设:前面已经按照右端大小把整个数组排序好了,那么若箭的位置不往右移,就有可能会漏了原本可以一箭射过去的气球
//若不以右端作为判断基准排好序,则数组中的线段位置过于杂乱,应用贪心的思想就会耗费很多性能
for(int i=1;i<points.length;i++){
if(points[i][0]>arrow1){//没有交集了,得再拿一支箭出来了
arrow1=points[i][1];
count++;
}
}
return count;
}
}