代码随想录算法训练营第三十五天| LeetCode 860.柠檬水找零、406.根据身高重建队列、452. 用最少数量的箭引爆气球

一、LeetCode 860.柠檬水找零

题目链接/文章讲解/视频讲解:https://programmercarl.com/0860.%E6%9F%A0%E6%AA%AC%E6%B0%B4%E6%89%BE%E9%9B%B6.html

状态:已解决

1.思路 

        这题实则非常简单,没有很多花里胡哨的,只需要模拟全部情景即可。

(1)账单是5,则不用找钱,直接收下5。

(2)账单是10,找钱5,收下10。

(3)账单是20,此时有两个找钱方案:找钱10,收下5;找三个5。优先前者

此时就可以发现 情况一,情况二,都是固定策略,不用做什么分析了,而唯一不确定的其实在情况三。而情况三逻辑也不复杂甚至感觉纯模拟就可以了,其实情况三这里是有贪心的。账单是20的情况,为什么要优先消耗一个10和一个5呢?

因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!

所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。局部最优可以推出全局最优,并找不出反例。

2.代码实现

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        map<int,int> charge;
        charge[5] = 0;
        charge[10] = 0;
        charge[20] = 0;
        for(auto bill:bills){
            charge[bill]++;
            if(bill==10){
                charge[5]--;
            }else if(bill==20){
                if(charge[10]>0) charge[10]--,charge[5]--;
                else charge[5] -= 3;
            }
            if(charge[5]<0){
                return false;
            }
        }
        return true;
    }
};

二、406.根据身高重建队列

题目链接/文章讲解/视频讲解:https://programmercarl.com/0406.%E6%A0%B9%E6%8D%AE%E8%BA%AB%E9%AB%98%E9%87%8D%E5%BB%BA%E9%98%9F%E5%88%97.html

状态:已解决

1.思路 

        这道题跟昨天的135题有异曲同工之妙,同样都是两个维度。对于两个维度的题,一定要先考虑完一个维度,再反过来考虑另一个维度,并进行适当的权衡。这题比较困惑的点是先确定k还是先确定h呢,也就是究竟先按h排序呢,还是先按照k排序呢?如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。那么按照身高h来排序呢,身高一定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!那么只需要按照k为下标重新插入队列就可以了,为什么呢?如图

实则这里有两个巧思点:

(1)首先,我们只可能把后面的数往前面移,不可能前面的数往后边移:假如存在某个人需要我们将他往后移动,那么就说明这个人的h值大于现在的下标索引 i ( h > i ),进一步说明前面比它高的人不足h,但由于已经排好序了,这个数前面的人一定比它高,后面没有再比他高的人,比它高的人只有i个,即使往后移动,也不能增加前面的人比它高的个数,因此只要数据正确,不可能会出现( h > i)的情况(排序后的i)。

(2)遍历到i时,因为已经排好序,i前面的人的身高都比它高,因此要前面比他高的人为h个,把他移到下标为h的位置即可,由于i是往前插入,故i插入的位置到i这个范围内的所有人都比i要高,因此将i插入前方不会影响原本区间[0,i]中任何一个人的实际h值(不会增加高个子的数量)。

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

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

找不出反例,直接贪心。

2.代码实现

class Solution {
public:
    static bool cmp(const vector<int>& v1, const vector<int>& v2){
    //第一元素相等时,再比较第二元素
        if (v1[0] == v2[0])
            return v1[1] < v2[1];
        return v1[0] > v2[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++){
            vector<int> person = people[i];
            que.insert(que.begin()+person[1],people[i]);//注意插入时的代码技巧
        }
        return que;
    }
};

三、452. 用最少数量的箭引爆气球

题目链接/文章讲解/视频讲解:https://programmercarl.com/0452.%E7%94%A8%E6%9C%80%E5%B0%91%E6%95%B0%E9%87%8F%E7%9A%84%E7%AE%AD%E5%BC%95%E7%88%86%E6%B0%94%E7%90%83.html

状态:已解决

1.思路       

        画图后根据常识,第一反应就是我们尽量射重叠最多的气球,用的弓箭一定最少,那么有没有当前重叠了三个气球,我射两个,留下一个和后面的一起射这样弓箭用的更少的情况呢?尝试一下举反例,发现没有这种情况。因此进行贪心。

        局部最优:当气球出现重叠,一起射,所用弓箭最少。全局最优:把所有气球射爆所用弓箭最少。

        这个贪心思路很容易就想到了,关键是如何去组织代码。

        当然可以直接射掉一个就从数组中remove一个元素,非常直观,毕竟气球被射了。然后并没有这个必要,我们只需要把气球排序之后,从前到后遍历气球,被射过的气球仅仅跳过就行了,不必让remove元素,只要记录一下箭的数量就可以了。

        因为需要查看重叠情况,因此需要先对气球数组进行排序。那么按照气球起始位置排序,还是按照气球终止位置排序呢?其实都行,只是不同的排序方式对应的遍历顺序不同而已。一般采用的都是根据起始位置进行排序。既然按照起始位置排序,那么就从前向后遍历气球数组,靠左尽可能让气球重复。

        如果真的重叠的气球了怎么办?怎么进行判断?很简单,points[i][0] <= points[i-1][1] 就代表气球重叠了(上一个气球的右边界大于这个气球的左边界)。那如果下一个气球也重叠了,又怎么进行比较呢?这里稍稍有点绕,因为右边界取的是已经重叠的气球中,右边界最小的那个,看图就明白了

可以看出首先第一组重叠气球,一定是需要一个箭,气球3,的左边界大于了 第一组重叠气球的最小右边界,所以再需要一支箭来射气球3了。

2.代码实现

class Solution {
public:
    static bool cmp(const vector<int>& v1, const vector<int>& v2){
    //第一元素相等时,再比较第二元素
        if (v1[0] == v2[0])
            return v1[1] < v2[1];
        return v1[0] < v2[0];
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        sort(points.begin(),points.end(),cmp);
        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],points[i-1][1]);
            }
        }
        return result;
    }
};

时间复杂度:O(nlog n),因为有一个快排

空间复杂度:O(1),有一个快排,最差情况(倒序)时,需要n次递归调用。因此确实需要O(n)的栈空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值