452用最少数量的箭引爆气球
题目描述:
有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points
,其中points[i] = [xstart, xend]
表示水平直径在 xstart
和 xend
之间的气球。你不知道气球的确切 y 坐标。
一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x
处射出一支箭,若有一个气球的直径的开始和结束坐标为 x
start
,x
end
, 且满足 xstart ≤ x ≤ x
end
,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。
给你一个数组 points
,返回引爆所有气球所必须射出的 最小 弓箭数 。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]] 输出:2 解释:气球可以用2支箭来爆破: -在x = 6处射出箭,击破气球[2,8]和[1,6]。 -在x = 11处发射箭,击破气球[10,16]和[7,12]。
示例 2:
输入:points = [[1,2],[3,4],[5,6],[7,8]] 输出:4 解释:每个气球需要射出一支箭,总共需要4支箭。
示例 3:
输入:points = [[1,2],[2,3],[3,4],[4,5]] 输出:2 解释:气球可以用2支箭来爆破: - 在x = 2处发射箭,击破气球[1,2]和[2,3]。 - 在x = 4处射出箭,击破气球[3,4]和[4,5]。
代码:
class Solution {
public:
// 定义一个比较函数 cmp,用于在排序时比较两个气球的起始点
static bool cmp(const vector<int> &a, const vector<int> &b) {
return a[0] < b[0]; // 根据气球的起始坐标进行升序排序
}
// 主函数,负责计算需要的最小箭数
int findMinArrowShots(vector<vector<int>>& points) {
// 如果气球数组为空,则需要的箭数为0
if (points.size() == 0) return 0;
// 根据气球的起始坐标对气球进行排序
sort(points.begin(), points.end(), cmp);
// 初始化结果为1,因为至少需要一支箭来击中第一个气球
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][1], points[i][1]);
}
}
// 返回所需的最小箭数
return result;
}
};
代码分析:
-
排序气球: 首先对气球的起始位置进行排序,这样可以方便地检查气球之间是否存在重叠。
-
初始化箭数:
result
用于统计最小箭数,初始设为 1,因为至少需要一支箭来击中最左侧的气球。 -
遍历比较:
- 检查当前气球是否与前一个气球重叠。
- 如果不重叠(即当前气球的起始点大于前一个气球的结束点),则需要增加一支箭。
- 如果重叠,则更新当前气球的结束位置为与前一个气球重叠部分的最小结束点。这是因为一支箭可以同时击中所有重叠的气球,从而减少箭的数量。
-
返回结果: 遍历完所有气球后,返回所需的最小箭数
result
。
这个逻辑确保算法的时间复杂度为 O(n log n),主要是由于排序过程,而遍历气球的过程时间复杂度为 O(n)。
452用最少数量的箭引爆气球
力扣题目链接
题目描述:
给定一个区间的集合 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){
return a[0]<b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.size()==0)return 0;
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]){
continue;
}else{
//这题与上一题的不同之处就是更新边界的时候result++因为更新边界的时候相当于移除了
这个重叠的区间
intervals[i][1]=min(intervals[i][1],intervals[i-1][1]);
result++;
}
}
return result;
}
};
//这题与上一题的不同之处就是更新边界的时候result++因为更新边界的时候相当于移除了这个重叠的区间
可以改变if里的条件,不仅是语法,也是另一个if逻辑
class Solution {
public:
static bool cmp(vector<int> &a,vector<int> &b){
return a[0]<b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if(intervals.size()==0)return 0;
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;
}
};
官方代码:(我没看这个,等有时间想不开了再来看)
class Solution {
public:
// 按照区间右边界排序
static bool cmp (const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
if (intervals.size() == 0) return 0;
sort(intervals.begin(), intervals.end(), cmp);
int count = 1; // 记录非交叉区间的个数
int end = intervals[0][1]; // 记录区间分割点
for (int i = 1; i < intervals.size(); i++) {
if (end <= intervals[i][0]) {
end = intervals[i][1];
count++;
}
}
return intervals.size() - count;
}
};
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) {
// 创建一个数组 hush,用于记录每个字母最后出现的位置
int hush[27] = {0}; // 26个字母 + 1 作为补位
// 遍历字符串,记录每个字母在s中最后出现的索引
for (int i = 0; i < s.size(); i++) {
hush[s[i] - 'a'] = i; // 使用 'a' 的ASCII值作为偏移量,将字母映射到数组索引
}
// 初始化右边界和左边界
int right = 0; // 目前找到的右边界
int left = 0; // 当前片段的左边界
vector<int> result; // 用于存储每个片段的长度
// 遍历字符串,计算每个片段的长度
for (int i = 0; i < s.size(); i++) {
// 更新当前字母的右边界
right = max(right, hush[s[i] - 'a']); // 取当前字母最后出现位置和当前右边界中的最大值
// 如果当前位置i达到了当前右边界,说明可以确定一个片段
if (i == right) {
// 将当前片段长度加入结果
result.push_back(right - left + 1); // 计算当前片段的长度,并加入result
left = right + 1; // 更新左边界,继续寻找下一个片段
}
}
// 返回所有片段的长度
return result;
}
};
代码分析:
-
记录字符最后出现位置:
- 使用一个大小为 27 的整型数组
hush
来记录每个字母最后出现的位置(下标为 0 - 25 代表字母 a - z,补位为 0)。
- 使用一个大小为 27 的整型数组
-
遍历字符串获取索引:
- 第一个
for
循环遍历字符串s
,对每个字符计算其在字符串中最后出现的位置并存储在hush
数组中。
- 第一个
-
初始化边界指针:
right
用于表示当前片段可以包含的最右字符位置,left
用于表示当前片段的最左边界。
-
确定片段:
- 第二个
for
循环遍历字符串,通过更新right
指针以找到片段的结束位置。 - 每当
i
到达right
,即当前索引等于当前片段的右边界,说明可以确定一个完整片段的长度。 - 使用
result.push_back(right - left + 1)
计算并记录这个片段的长度,更新left
为right + 1
,以便查找下一个片段。
- 第二个
-
返回结果:
- 最后返回包含所有片段长度的
result
向量。
- 最后返回包含所有片段长度的
这个算法的时间复杂度是 O(n),其中 n 是字符串 s
的长度。因为我们最多遍历字符串两次(一次记录字符最后的位置,一次划分片段)。