31.贪心(5) | 无重叠区间、划分字母区间(h)、合并区间

        今天的贪心算法题目都是区间相关,大都需要排序。涉及到排序,重点是要把cmp()函数的参数设置为引用类型,以及排序对应的时间、空间复杂度。


        第1题(LeetCode 435. 无重叠区间)自己AC的思路是按照day 35中第3题(LeetCode 452. 用最少数量的箭引爆气球)的方式统计出不重叠区间数,再用总区间数减去不重叠区间数,就得到需要去除的区间数。与其不同的是,这一题认为两个区间恰好相邻的话,不是重叠区间。

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b) { // 要按引用类型传参
        return a[0] < b[0]; // 按照end排序也可以
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), cmp);
        int end = intervals[0][1];
        int cnt = 1;
        for (int i = 1; i < intervals.size(); ++i) {
            if (intervals[i][0] < end) {
                end = min(end, intervals[i][1]);
            }
            else {
                cnt++;
                end = intervals[i][1];
            }
        }
        return intervals.size() - cnt;
    }
};

        题解的整体思路与自己的一致,但统计不重复区间数的实现方法不同。首先,排序和遍历方式有2种:

  • 按照每个区间的右边界从小到大排序,那么就要按照右边界从小到大,即从左到右的顺序遍历。因为右边界越小,留给之后(右边)区间的空间就越大,就越不容易发生重叠。
  • 按照每个区间的左边界从小到大排序,那么就要按照左边界从大到小,即从右到左的顺序遍历。因为左边界越大,留给之后(左边)区间的空间就越大,就越不容易发生重叠。

如果选择第1种,那么就将重叠区间右边界初始化为第一个区间的右边界。在遍历时,遇到区间的左边界大于或等于重叠区间右边界时,就对不重复区间数+1,并更新重叠区间右边界为当前区间的右边界。

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b) { // 要按引用类型传参
        return a[1] < b[1];
    }
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), cmp);
        int end = intervals[0][1];
        int cnt = 1;
        for (int i = 1; i < intervals.size(); ++i) {
            if (intervals[i][0] >= end) {
                cnt++;
                end = intervals[i][1];
            }
        }
        return intervals.size() - cnt;
    }
};

        因为使用了sort(),所以空间复杂度至少是O(n)。因为最坏情况下的快速排序(恰好有序)会进行n次递归调用,所以会占用O(n)的栈空间。

        二刷:做出来了,但不熟悉。


        第2题(LeetCode 763.划分字母区间)自己AC的思路是首先统计每个字母出现的最大下标,然后遍历s,使每一段都尽可能地短。所以,需要预设一个最短段下标,将其设置为第一个字母出现的最大下标,并在遍历时不断根据新字母出现的最大下标,将其更新为更大的值。只要遍历到该最短段下标,就说明段内的所有字母都出现在当前段内,不会出现在之后,于是就可以将当前遍历过的部分当作一段,并开始下一段的遍历。

class Solution {
public:
    vector<int> partitionLabels(string s) {
        vector<int> indsMax(26, 0);
        for (int i = 0; i < s.size(); ++i) {
            indsMax[s[i] - 'a'] = i;
        }
        vector<int> res;
        int indMaxCur = indsMax[s[0] - 'a'], indMaxPre = -1;
        for (int i = 0; i < s.size(); ++i) {
            indMaxCur = max(indMaxCur, indsMax[s[i] - 'a']);
            if (i == indMaxCur) {
                res.push_back(indMaxCur - indMaxPre);
                indMaxPre = indMaxCur;
            }
        }
        return res;
    }
};

需要注意因为结果需要以各段段长度的方式呈现,所以第9行的indMaxPre应该初始化为-1,代表上一个段的段尾下标是-1。

        题解的思路和实现方式与自己一样。

        二刷:方法一时没想起来,最后做出来了。


        第3题(LeetCode 56. 合并区间)自己AC的思路还是首先对区间按照左边界由小到大排序,再做进一步处理。排序后,将首个区间的左边界设置为begin,右边界设置为end,然后开始遍历剩余区间,贪心目标是让当前区间尽可能多地与其他区间合并。如果遇到区间的左边界不大于end,就将其合并,更新end;如果遇到区间的左边界大于end,就将begin和end保存进结果,然后更新begin和end为当前区间。

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b) {
        return a[0] < b[0];
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end(), cmp);
        vector<vector<int>> res;
        int begin = intervals[0][0], end = intervals[0][1];
        for (int i = 1; i < intervals.size(); ++i) {
            if (intervals[i][0] <= end) {
                end = max(end, intervals[i][1]);
            }
            else {
                res.push_back({begin, end});
                begin = intervals[i][0];
                end = intervals[i][1];
            }
        }
        res.push_back({begin, end});
        return res;        
    }
};

代码中需要注意2个问题:

  • 再次注意cmp的参数设置为引用类型,这次就又忘记了,导致时间、空间占用都很高。
  • 以及第15行,向vector<vector<int>>添加含2个元素的vector的方式是使用花括号。只有向容器中数据类型为pair的数据结构中添加元素时,才可用pair。比如向vector<pair<int, int>>中添加元素就用pair<int, int>(a, b)。具体可参考day 21中第2题。

另外还要第20行,要把最后一个合并后的区间也添加上。

        题解的思路、方法与自己的基本一致,实现方法上有2点不同。第一点是在结果上,并非在区间无法再合并时才将begin和end添加进结果,而是从一开始就添加进去,然后不断的通过.back()取结果vector的末尾元素进行修改;第二点是sort()中的cmp函数参数使用了lambda表达式,这个在之前没有接触过。通过实验,这种方式与定义引用类型参数的cmp函数时间、空间效率基本一致,都比值传递参数的cmp函数效率高很多。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值