day30-贪心__452. 用最少数量的箭引爆气球__435. 无重叠区间__763.划分字母区间
452. 用最少数量的箭引爆气球
对于这道题来说,初看的时候,我们会认为复杂繁琐,但是如果我们对每一个vector 按照左端点或者按照右端点进行排序之后,我们去射击气球时,每次射击都尽可能的设计区间的重叠部分,这样就能保证我们花费的箭最少。那么如何判断气球之间是否发生重合呢
根据题目示例的[[10,16],[2,8],[1,6],[7,12]],我们将其按照左端点从小到大能画出这样的图,那只要后一段的左端点小于前一段的右端点时,这两段就发生了重叠。只需要一只箭,那重叠之后,我们就需要向后判断了,这个时候问题就来了我们判断第三段的时候,是使用第一段的右端点还是第二段的右端点来判断呢。显然我们这里应该选择的判断目标应该是min(第一段的右端点, 第二段的右端点), 这样才能达到,我们想要判断第三段和前两段是否同时重叠的目的!
有了上述的思考后,我们就很容易想到,对排序后的数组做一次遍历。每一次都去判断前一段的右端点,是否大于等于后一段的左端点。如果右端点大于左端点,更新新的右端点为min(前一段右端点, 后一段右端点),然后在继续循环。如果不满足条件,result++。
代码如下:
class Solution {
private:
static bool cmp(const vector<int>& a, const vector<int>& b) {
return a[0] < b[0];
}
public:
int findMinArrowShots(vector<vector<int>>& points) {
if (points.size() == 0) return 0;
sort(points.begin(), points.end(), cmp);
int result = 1; // points 不为空至少需要一支箭
for (int i = 1; i < points.size(); i++) {
if (points[i][0] > points[i - 1][1]) { // 气球i和气球i-1不挨着,注意这里不是>=
result++; // 需要一支箭
}
else { // 气球i和气球i-1挨着
points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界
}
}
return result;
}
};
435. 无重叠区间
这道题其实和上一题很类似,像当于换了一个马甲。计算要移除的重叠区间个数。判断重叠区间的方法和上一题是一样的只是不包含(前一段右端点==后一段左端点)这种情况,所以当出现(前一段的右端点 < 后一段的左端点)时,我们判断出发生重叠。这个时候就要移除某一段区间,那么是移动第一段还是第二段呢? 这里其实我们要移除两端中右端点更小的哪一段,因为只有这样才能保证后一段(第三段)能够更小概率和这一段重叠。但是由于这道题,只需要记录移除的段数,所以我们不需要去对这个数组做移除操作,只需要将第二段的右端点更新为min(第一段右端点,第二段右端点)即可。
代码如下:
class Solution {
public:
static bool cmp(vector<int> a,vector<int> b)
{
return a[0] < b[0];
}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
sort(intervals.begin(),intervals.end(),cmp);
int ans = 0;
for(int i=1;i<intervals.size();i++)
{
if(intervals[i-1][1] > intervals[i][0]) // 发生重叠
{
intervals[i][1] = min(intervals[i][1], intervals[i-1][1]);
ans++;
}
}
return ans;
}
};
763.划分字母区间
这道题看起来相当的复杂,因为在我们动态计算最远划分距离的过程中,会不断添加新的字母进来,导致我们的动态划分距离要考虑的字母数越来越多,这显然是很复杂的。
但是我们不妨先对这个字符串,进行一次访问,记录出这个字符串中,每个字母的最远出现位置。(使用类似桶排序的方式)
代码如下:
int hash[27] = {0};
// 记录每个字母的最远出现位置
for(int i=0;i<s.size();i++)
{
hash[s[i]-'a'] = i;
}
有了这个数组之后,我们可以记录一个right变量,并将其初始化为hash[s[0] - ‘a’],即字符串中0号位置元素的最远距离,然后开始遍历,没遍历一个元素就尝试去更新right,更新的方式为max(right, hash[s[i] - ‘a’]),即right永远保持为当前遍历过字母的最远出现位置。那我们什么时候记录一个答案呢? 当然是right == i 的时候,即我们已经走到了当前遍历过字母的最远位置,这时我们需要记录答案,而由于这道题要求我们保存的是每一段的元素个数,所以我们除了right之外,还需要一个left,用来记录我们开始时的左端点。
最终代码如下:
class Solution {
public:
vector<int> partitionLabels(string s) {
int hash[27] = {0};
// 记录每个字母的最远出现位置
for(int i=0;i<s.size();i++)
{
hash[s[i]-'a'] = i;
}
vector<int> ans;
int left = 0,right = 0;
for(int i=0;i<s.size();i++)
{
right = max(right,hash[s[i] - 'a']);
if(right == i)
{
ans.push_back(right - left + 1);
left = i+1;
}
}
return ans;
}
};