
引言
初篇我们介绍了贪心算法的相关背景知识,本篇我们将结合具体题目,进一步深化大家对于贪心算法的理解和运用。
一、坏了的计算器
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;
}
};
小结
本篇关于贪心算法的介绍就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!