贪心算法篇——万千抉择中的唯一考量,最优解追寻的跬步累积(7)


在这里插入图片描述

引言

初篇我们介绍了贪心算法的相关背景知识,本篇我们将结合具体题目,进一步深化大家对于贪心算法的理解和运用。

一、坏了的计算器

1.1 题目链接:https://leetcode.cn/problems/broken-calculator/description/

1.2 题目分析:

  • 在显示着数字 startValue 的坏计算器上,我们可以执行以下两种操作:

双倍(Double):将显示屏上的数字乘 2;
递减(Decrement):将显示屏上的数字减 1 。

  • 给定两个整数 startValue 和 target 。返回显示数字 target 所需的最小操作数。

1.3 思路讲解:

将startValue 理解为begin,target理解为end

贪⼼策略:
正难则反:
当「反着」来思考的时候,我们发现:
i. 当 end <= begin 的时候,只能执⾏「加法」操作;
ii. 当 end > begin 的时候,对于「奇数」来说,只能执⾏「加法」操作;对于「偶数」来说,最好的⽅式就是执⾏「除法」操作这样的话,每次的操作都是固定唯一的

1.4 代码实现:

class Solution {
public:
    int brokenCalc(int startValue, int target) {
        int ret=0;//操作次数
        while(target>startValue)
        {
            if(target%2==0) target/=2;//如果为偶数,直接除以二
            else target++;//修正为偶数
            ret++;
        }
        return ret+startValue-target;//注意次数并非直接返回ret
    }
};

解释:

  • while (target > startValue):当 target 大于 startValue 时,我们会通过加倍的逆操作(除以 2)或者递增操作来调整 target。

  • if (target % 2 == 0):如果 target 是偶数,那么我们可以通过除以 2 的操作来逆转加倍操作。

  • else:如果 target 是奇数,那么只能通过递增 1 来使其变为偶数,之后可以继续进行除以 2 操作。

  • return ret + startValue - target:当 target 小于或等于 startValue 时,我们只需对 startValue 进行递减操作。此时,操作次数就是 startValue - target。

二、合并区间

2.1 题目链接:https://leetcode.cn/problems/merge-intervals/description/

2.2 题目分析:

  • 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
  • 请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

2.3 思路讲解:

观察测试样例可知,在合并为一个区间时,应该选取左区间尽可能小的,右区间尽可能大的进行合并,这样子可以保证最大化覆盖区间。

具体步骤如下:

  • 我们可以按左区间排序的方式先对整个数组intervals进行预处理,然后会发现,能够合并的区间,都是连续
  • 然后从左往右,按照求并集的方式合并区间

贪心策略:
由于区间已经按照「左端点」排过序了,因此当两个区间「合并」的时候,合并后的区间:

  • 左端点就是「前⼀个区间」的左端点;
  • 右端点就是两者「右端点的最⼤值」。

2.4 代码实现:

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        //按照左端点进行排序
        sort(intervals.begin(),intervals.end(),[&](vector<int> a,vector<int> b)
        {
            return a[0]<b[0];
        });
        int left=intervals[0][0],right=intervals[0][1];
        int n=intervals.size();
        vector<vector<int>> ret;//返回数组
        for(int i=1;i<n;i++)
        {
            int a=intervals[i][0],b=intervals[i][1];
            //判断是否存在重叠区间
            if(right>=a)
            {
                right=max(right,b);//更新右端点
            }
            else//不存在重叠区间,直接添加
            {
                ret.push_back({left,right});
                left=a,right=b;//更新左右端点

            }

        }
        ret.push_back({left,right});//别忘了最后一个区间
        return ret;
        
    }
};

三、无重叠区间

3.1 题目链接:https://leetcode.cn/problems/non-overlapping-intervals/description/

3.2 题目分析:

  • 给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
  • 注意 只在一点上接触的区间是 不重叠的。例如 [1, 2] 和 [2, 3] 是不重叠的。

3.3 思路讲解:

本题与上题类型,但是本题要求是移除区间,同理,移除区间时应该选取左区间尽可能小的,右区间尽可能大的进行合并,这样子可以保证最大化覆盖区间

具体步骤如下:
贪⼼策略:

  • 按照「左端点」排序;
  • 当两个区间「重叠」的时候,为了能够「在移除某个区间后,保留更多的区间」,我们应该把「区间范围较⼤」的区间移除。

如何移除区间范围较⼤的区间:
由于已经按照「左端点」排序了,因此两个区间重叠的时候,我们应该移除「右端点较⼤」的区间.

3.4 代码实现:

class Solution {
public:
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        int ret=0;
        //按左端点进行排序
      sort(intervals.begin(),intervals.end());
      int left=intervals[0][0],right=intervals[0][1];
      int n=intervals.size();
      for(int i=1;i<n;i++)
      {
        int a=intervals[i][0],b=intervals[i][1];
        //有重叠部分
        if(a<right)
        {
            ret++;//删除一个区间
            right=min(right,b);//更新右端点,注意删除较大的区间
        }
        else
        //无重叠部分
        {
            left=a;
            right=b;//更新端点,无需删除

        }
      }
      return ret;
    }
};

四、用最少数量的箭引爆气球

4.1 题目链接:https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/description/

4.2 题目分析:

  • points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球,即气球的宽度
  • 若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆
  • 给你一个数组 points ,返回引爆所有气球所必须射出的最小 弓箭数 。

4.3 思路讲解:

翻译过后的题目分析如上,与合并区间类似,最终所需要的弓箭数量即为合并后的区间数量。

但是本题需要注意,相邻区间无法合并
在这里插入图片描述
并且,本题要求合并区间求的是交集是气球都必须含有的公共部分。

4.4 代码实现:

class Solution {
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        int n=points.size();
        if(n==1) return 1;//处理特殊情况
        int ret=1;
        sort(points.begin(),points.end());//按照左端点进行排序
        int left=points[0][0],right=points[0][1];
        for(int i=1;i<n;i++)
        {
            int a=points[i][0],b=points[i][1];
            //有重叠部分
            if(a<=right)
            {
                right=min(right,b);//求并集
               
            }
            else
            //无重叠部分
            {
                ret++;
                
                right=b;
            }
        }
        return ret;
        
    }
};

小结

本篇关于贪心算法的介绍就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值