Day 36 贪心算法 P5

435. 无重叠区间

代码随想录

1. 思路

这道题的思路和射箭非常像,因此可以直接利用射箭的结果。但其实复杂了,有更简单的方法。

(1)射箭——左排序

射箭这道题跳过的部分就是这道题需要计数的部分。但是射箭题right>=left需要跳过,本题只要right>left就可以计数。这是因为如果有这样两段,[1,2][2,3],射箭的时候,射到2就可以双穿,所以是>=,但是这道题中这两段交集为1并没有长度,因此不计数。说白了就是点和线的区别。

(2)射箭——逆转结果

可以直接将size()-射箭个数作为结果。但正如(1)中所说的,射箭的判断条件要从right<=left改成right<left。这是因为射箭个数就是不重合片段个数,而重合片段个数最多为size。

(3)右排序

因为重合片段个数最多为size,所以只要顺序后有两个不同线段有交集,就可以计数。所以,不用判断一共有多少个在一个重合中,可以直接遍历顺序右侧,看两两是否有交集。

之所以右排序,因为我们是从左到右遍历的,每跨越一个线段相当于抹除了左侧相交的线断,所以如果用左排序是不合理的。

class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        return a[0] < b[0];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), cmp);
        int left = intervals[0][0];
        int right = intervals[0][1];
        int num = 0;
        for(int i=1; i<intervals.size(); i++){
            left = intervals[i][0];
            if(right>left){
                num++;
                right = min(right,intervals[i][1]);
            }else{
                right = intervals[i][1];
            }
        }
        return num;
    }
};

763. 划分字母区间

代码随想录

1. 思路

思路就是转化到上面那道题,先构建出顺序的区间,之后判断不重复的条件。条件就是当前面一个right<下面一个left。

但其实有更简单的方法。因为这个条件等价于,不同的线段属于不同堆。因此,可以记录字母最后一次出现的位置。然后从前往后遍历,如果遇到max==index,说明这属于一堆。相当于用位置对应位置进行记录。

class Solution {
public:
    vector<int> partitionLabels(string S) {
        int hash[27] = {0}; // i为字符,hash[i]为字符出现的最后位置
        for (int i = 0; i < S.size(); i++) { // 统计每一个字符最后出现的位置
            hash[S[i] - 'a'] = i;
        }
        vector<int> result;
        int left = 0;
        int right = 0;
        for (int i = 0; i < S.size(); i++) {
            right = max(right, hash[S[i] - 'a']); // 找到字符出现的最远边界
            if (i == right) {
                result.push_back(right - left + 1);
                left = i + 1;
            }
        }
        return result;
    }
};

56. 合并区间

代码随想录

1. 思路

我的思路是,记录重合区间的起始和重点位置,然后遍历,一个区域结束以后就保存结果

但是这样做有很多细节需要处理,比如最后一个区间如果是独立的,记录不上。比如只有两个区间,来不及更新。因此需要多写很多条件。

因此,有更简单的方法,就是在result里面不断更新,而不是更新后再存进去

在结果上更新在很多时候可以显著降低算法分类成本,但是需要算法是线性推进的。贪心算法本质上就是线性推进、全局最优的,因此可以这样做。比如之前震荡数列和气球在原数组上更新left或right,和这道题在result里更新。

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        vector<vector<int>> result;
        if (intervals.size() == 0) return result; // 区间集合为空直接返回
        // 排序的参数使用了lambda表达式
        sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b){return a[0] < b[0];});

        // 第一个区间就可以放进结果集里,后面如果重叠,在result上直接合并
        result.push_back(intervals[0]); 

        for (int i = 1; i < intervals.size(); i++) {
            if (result.back()[1] >= intervals[i][0]) { // 发现重叠区间
                // 合并区间,只更新右边界就好,因为result.back()的左边界一定是最小值,因为我们按照左边界排序的
                result.back()[1] = max(result.back()[1], intervals[i][1]); 
            } else {
                result.push_back(intervals[i]); // 区间不重叠 
            }
        }
        return result;
    }
};

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值