LeetCode题解 贪心(四):406 根据身高重建队列;452 用最少数量的箭引爆气球;435 无重叠区间

406 根据身高重建队列 medium

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)

本道题目涉及两个维度,身高h,以及前面身高大于等于自身的人数k

两个维度,总要先确定一个维度,在这道题目中就是身高,因为k同样与身高有关,而身高本身则是一个独立的变量。

我们先按照身高从大到小排序(之所以这么做是因为k表示前面有多少身高大于等于自己的人)

然后,就可以根据第二个维度,选择插入的位置。

以官方示例1为例:

[[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]为打乱后的数组,

首先按照身高排序:[[7,0] [[7,1] [6,1] [5,0] [5,2] [4,4]],再按照k的大小,从前到后进行插值,最终就可以得到

[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

根据以上思路,代码如下:

static bool cmp(const vector<int>& a, const vector<int>& b) {
    if (a[0] == b[0]) return a[1] < b[1];
    return a[0] > b[0];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
    sort(people.begin(), people.end(), cmp);
    vector<vector<int>> que;
    for (int i = 0; i < people.size(); i++) {
        int index = people[i][1];
        que.insert(que.begin() + index, people[i]);
    }

    return que;
}

根据随想录中的说法,这道题之所以可以放在贪心系列,因为;

局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性

全局最优:最后都做完插入操作,整个队列满足题目队列属性

但我认为这道题就是一道模拟题,局部与全局的说法,更像是上下两部分,而非从属的关系。

从上述代码中我们可以看出,这道题目还有可以优化的空间,因为插入操作对于vector而言,涉及到扩容的问题,效率实际上并不高,随想录中也提出,可以使用list容器,最终将结果复制到vector数组中即可,代码如下:

static bool cmp(const vector<int>& a, const vector<int>& b) {
    if (a[0] == b[0]) return a[1] < b[1];
    return a[0] > b[0];
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
    sort (people.begin(), people.end(), cmp);
    list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
    for (int i = 0; i < people.size(); i++) {
        int position = people[i][1]; // 插入到下标为position的位置
        std::list<vector<int>>::iterator it = que.begin();
        while (position--) { // 寻找插入位置
            it++;
        }
        que.insert(it, people[i]);
    }
    return vector<vector<int>>(que.begin(), que.end());
}

452 用最少数量的箭引爆气球 medium

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的最小弓箭数 。

这道题透露着一股贪心的味道

如果想要用最少的箭完成这件事,肯定先选覆盖气球最多的位置射箭,那么问题就在怎么知道哪个位置的气球多呢?

方法就是球气球之间的覆盖范围,我们先按照起始位置对原数组进行排序,再根据每个气球的结束为止是否在下一个气球的起始范围内,来判断是不是需要多射一枝箭。根据这个思路,代码如下:

static bool cmp(const vector<int>& a, const vector<int>& b) {
    return a[0] < b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
    sort(points.begin(), points.end(), cmp);

    int res = 1;    //因为最少也要用一支箭
    for (int i = 0; i < points.size() - 1; ++i) {
        if (points[i][1] < points[i + 1][0]) {
            res++;
        }else {
            points[i + 1][1] = min(points[i + 1][1], points[i][1]);
        }
    }

    return res;
}

值得注意的是,当一支箭贯穿了该范围内的所有气球时,为了避免其他气球也以为自己在这个范围内,需要对右边界进行更新,例如下图,如果不及时更新第二个气球的右边界,另其为与之前一个气球右边界的最小值,那么第三格气球就会认为自己也可以同时被射爆:

  ■■■
■■■■■■■
     ■■■■ 

435 无重叠区间 medium

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠

这道题目和上一题有异曲同工之妙

如果要使移除区间的数量最小,我们首先会想到移除覆盖范围最广且与其他区间有重叠的区间,但是如何确定某个区间覆盖范围最广,是件难事。

我们采用与上一题相近的思路,首先按照起始位置的大小从小到大进行排序,如果起始位置一致,就按照终止位置从小到大进行排序。如果当前区间的起始位置大于等于上一个区间,意味着两个区间不重叠。但是如果不是,就说明两个区间重叠,更新重叠个数,然后再更新当前区间的终止位置为与上一个区间终止位置中的较小值。

根据这个思路,代码如下:

static bool cmp(const vector<int>& a, const 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 res = 0;
    for (int i = 1; i < intervals.size(); ++i) {
        if (intervals[i][0] < intervals[i - 1][1]) {
            res++;
            intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]);
        } 
    }
    return res;
}

说实话我也没想到一遍就过了,细想想,排序时就间接在做覆盖范围最广的判断,之后只要统计哪几个区间有重叠,就可以了

另外,这道题目还可以用动态规划求解,但是时空复杂度都没有贪心好。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值