452.用最少的数量的箭引爆气球(贪心算法)
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中points[i] = [xstart, xend]
表示水平直径在 xstart
和 xend
之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x
处射出一支箭,若有一个气球的直径的开始和结束坐标为 x
start
,x
end
, 且满足 xstart ≤ x ≤ x
end
,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points
,返回引爆所有气球所必须射出的 最小 弓箭数 。
class Solution {
public:
static bool cmp(vector<int>&a,vector<int>&b){
return a[0]<b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(),points.end(),cmp);
int result=1;
for(int i=1;i<points.size();i++){
//如果当前的气球位置最小值 大于 上一个气球位置的最大值 说明两个气球不重合 需要加一只弓箭
if(points[i][0]>points[i-1][1]){
result++;
}else{
//否则当前两个气球重合,选择两个气球中 最小的最大边界,然后将两个气球看作一个气球,继续进行
points[i][1]=min(points[i][1],points[i-1][1]);
}
}
return result;
}
};
1.整体思想
射爆气球有两种情况
1.当两个气球不重合的情况,需要加一只弓箭
2.当两只气球重合的时候,选择其中一只右边界最小的情况,将两个气球看作一个气球,用于下一次的迭代
2.逐步分析
代码中的 findMinArrowShots
函数实现了这个算法:
sort(points.begin(), points.end(), cmp)
:这行代码使用自定义的比较函数cmp
对points
进行排序,确保气球的左边界是递增的。int result = 1;
:初始化结果为 1,因为至少需要一支箭。for(int i=1; i<points.size(); i++)
:遍历排序后的气球数组。if(points[i][0] > points[i-1][1])
:检查当前气球是否与前一个气球重合。result++;
:如果不重合,需要额外的箭,因此结果加一。else
:如果重合,选择两个气球中右边界较小的那个。points[i][1] = min(points[i][1], points[i-1][1]);
:更新当前气球的右边界。return result;
:返回所需的最少箭数。
这个算法的时间复杂度是 O(nlogn),其中 n 是气球的数量,主要是因为排序操作。空间复杂度是 O(1),除了输入数组外没有使用额外的空间。
435.无重叠区间(贪心算法)
给定一个区间的集合 intervals
,其中 intervals[i] = [starti, endi]
。返回 需要移除区间的最小数量,使剩余区间互不重叠 。
示例 1:
输入: intervals = [[1,2],[2,3],[3,4],[1,3]] 输出: 1 解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:
输入: intervals = [ [1,2], [1,2], [1,2] ] 输出: 2 解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
输入: intervals = [ [1,2], [2,3] ] 输出: 0 解释: 你不需要移除任何区间,因为它们已经是无重叠的了
class Solution {
public:
static bool cmp(vector<int>&a,vector<int>&b){
if(a[0]==b[0])return a[1]<b[1];
return a[0]<b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(),intervals.end(),cmp);
int result=0;
//当前值的左边界小于前一个值的右边界时,两个集合重叠
for(int i=1;i<intervals.size();i++){
if(intervals[i][0]<intervals[i-1][1]){
//重叠之后,将两个集合的右边界选一个最小的右边界(删除重叠的集合),防止下次迭代的时候误判
intervals[i][1]=min(intervals[i][1],intervals[i-1][1]);
result++;
}
}
return result;
}
};
1.整体思想
集合重叠的情况是当前集合的左边界小于前一个集合的右边界,此时两个集合重叠,result++,删除重合的集合(选取其中集合的最小右边界),进行下次迭代。
2.逐步分析
算法的核心思想也是贪心算法。首先,我们将所有区间按照开始位置进行排序,如果开始位置相同,则按照结束位置进行排序。然后,我们从左到右遍历排序后的区间,如果当前区间的开始位置小于前一个区间的结束位置,这意味着两个区间重叠,我们需要移除一个区间。为了最小化移除的数量,我们选择移除结束位置较大的区间,因为这样可以留下更多的空间给后续的区间。
代码中的 eraseOverlapIntervals
函数实现了这个算法:
sort(intervals.begin(), intervals.end(), cmp)
:这行代码使用自定义的比较函数cmp
对intervals
进行排序,确保区间的开始位置是递增的,如果开始位置相同,则结束位置也是递增的。int result = 0;
:初始化结果为 0,表示需要移除的区间数量。for(int i=1; i<intervals.size(); i++)
:遍历排序后的区间数组。if(intervals[i][0] < intervals[i-1][1])
:检查当前区间是否与前一个区间重叠。intervals[i][1] = min(intervals[i][1], intervals[i-1][1]);
:如果重叠,选择结束位置较小的区间,并将其结束位置更新为两个区间结束位置的最小值。这实际上是模拟移除结束位置较大的区间。result++;
:每次更新区间结束位置时,都意味着我们移除了一个区间,因此结果加一。return result;
:返回需要移除的最小区间数量。
这个算法的时间复杂度是 O(nlogn),其中 n 是区间的数量,主要是因为排序操作。空间复杂度是 O(1),除了输入数组外没有使用额外的空间。
763.划分字母区间(贪心算法)
给你一个字符串 s
。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s
。
返回一个表示每个字符串片段的长度的列表。
示例 1:
输入:s = "ababcbacadefegdehijhklij" 输出:[9,7,8] 解释: 划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。 每个字母最多出现在一个片段中。 像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。
示例 2:
输入:s = "eccbbbbdec" 输出:[10]
class Solution {
public:
vector<int> partitionLabels(string s) {
vector<int> last(26);
for(int i=0;i<s.size();i++){
//实时更新当前元素的最远位置 //s[i]-'a' s中的元素在last的索引
last[s[i]-'a']=i;
}
vector<int> que;
int star=0;
int end=0;
for(int i=0;i<s.size();i++){
end=max(end,last[s[i]-'a']);
if(i==end){
que.push_back(end-star+1);
star=end+1;
}
}
return que;
}
};
int length = s.size();
获取字符串s
的长度并存储在变量length
中。for (int i = 0; i < length; i++) { ... }
是一个循环,遍历字符串s
中的每个字符。last[s[i] - 'a'] = i;
这行代码是关键。它计算当前字符在last
数组中的索引,并将其最后一次出现的位置(即索引i
)存储在对应的位置上。这里的s[i] - 'a'
是因为英文字母 ‘a’ 到 ‘z’ 可以用数字 0 到 25 来表示,所以我们通过减去 ‘a’ 来将字符转换为对应的索引。
例如,如果字符串 s
中字符 ‘c’(对应索引为 2)最后一次出现的位置是 10,那么 last[2]
将被赋值为 10。
int length = s.size();
for (int i = 0; i < length; i++) {
last[s[i] - 'a'] = i;
}
这个 last
数组用于后续的分割字符串操作,以便我们可以快速查找任何字符在字符串中的最后一次出现的位置。这是解决“分割字符串”问题的关键步骤,因为它允许我们确定每个分割片段的最小长度,以确保每个字母在整个字符串中只出现在一个片段中。
vector<int> que;
int star=0;
int end=0;
for(int i=0;i<s.size();i++){
end=max(end,last[s[i]-'a']);
if(i==end){
que.push_back(end-star+1);
star=end+1;
}
}
这段代码的功能是遍历字符串s
,使用一个队列que
来存储字符串中每个字符最后出现位置的最大值。这里,last
数组存储了每个字母最后出现的位置。代码的关键在于通过end
变量记录当前字符集的结束位置,而star
记录开始位置。当i
等于end
时,意味着当前字符集遍历完成,此时将字符集的长度加入队列que
中,然后更新star
为end+1
,表示新的字符集开始。这个队列que
最终会存储字符串s
中每个不同字符集的长度。