30.贪心(4) | 柠檬水找零、根据身高重建队列(h)、用最少数量的箭引爆气球(h)

        今天的题也是第1道相对简单,后两道虽然自己也做出来了,但都没有完全做对,有不同的坑。这种坑就导致有时自以为做对了没有问题,但实际不正确。在真正笔试或面试时只有正确率而没有结果错误的测试用例,就很难找出错误,所以需要多积累经验。


        第1题(LeetCode 860. 柠檬水找零)比较简单,自己实现的思路是当付的钱是5时,只能收着;付的钱是10时,只能找5元;只有收的钱是20时才有选择空间,可以找10 + 5元,或者找3个5元。而当付的钱是10元和20元时都需要找5元,只有收到20元时才需要找10元,所以贪心策略是遇到20元时优先找10 + 5元,不满足时才找3个5元。然后当10元或5元钱数小于0时即返回false。

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int n5 = 0, n10 = 0;
        for (int bill : bills) {
            if (bill == 5) {
                n5++;
            }
            else if (bill == 10) {
                n5--;
                n10++;
            }
            else {
                if (n10 > 0 && n5 > 0) {
                    n10--;
                    n5--;
                }
                else {
                    n5 -= 3;
                }
            }
            if (n5 < 0 || n10 < 0) {
                return false;
            }
        }
        return true;
    }
};

 题解的思路、实现方式与自己一致。


        第2题(LeetCode 406. 根据身高重建队列)自己实现的思路(错误的)是首先对people按照自己的位次(第2个属性)由小到大排序。此时,people中的所有元素一定是要么需要往前(左)移动,要么不需要移动的,没有需要往后(右)移动的。再循环遍历people。对于people中的每一个当前元素(对应下标i),都在内层循环从第0个元素开始遍历到当前元素为止,记录与当前元素身高相比更高或相等的人个数,一旦个数达到people[i][1],就将people[i]从原来位置取出,放入到达到个数的位置之后,以满足people[i][1]的要求。之后再对people[i + 1]进行同样的内部循环和处理,直到遍历完每一个元素。

class Solution {
public:
    static bool cmp(vector<int> a, vector<int> b) {
        return a[1] < b[1];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(), people.end(), cmp);
        for (int i = 0; i < people.size(); ++i) {
            int cnt = 0;
            for (int j = 0; j < i; ++j) {
                if (people[j][0] >= people[i][0]) {
                    ++cnt;
                    if (cnt == people[i][1] + 1) { // 是+1
                        people.insert(people.begin() + j, people[i]);
                        people.erase(people.begin() + i + 1);
                        break;
                    }
                }
            }
        }
        return people;
    }
};

需要注意第13行处的判断用了people[i][1] + 1而不是people[i][1],这是为了避免如果令cnt + 1的元素就是people[i]本身,people进行无意义的插入与删除。实现过程中用到了vector的insert()和erase()函数,需要再次熟悉下。也正因为用了这两个操作,其时间复杂度为O(n),导致总的时间复杂度达到O(n³)。

        但看了题解并思考后,发现自己上面的思路和实现虽然AC,但却是错误的。因为在每个元素左移的过程中,都可能会破坏之前已经左移过的元素的合法性。比如输入为[[7,0],[5,0],[7,2],[6,1],[5,2],[7,1]]时,上面错误代码的输出就会是[[7,0],[5,0],[7,1],[6,1],[7,2],[5,2]],而正确答案是[[5,0],[7,0],[5,2],[6,1],[7,1],[7,2]]。

        题解的正确思路是首先对people按照身高由大到小排序。这样一来,从左往右处理(插入到前面某位置)元素时,因为该元素身高是小于其左边所有元素的,所以不会影响到已经处理过元素的合法性。此外,可以用一个新的vector<vector<int>>来保存结果,当要插入某个元素(对应下标i)时,因为结果中保存的元素身高都不小于该元素,其目标插入位置就是people[i][1],不需再用一个循环来确定。

class Solution {
public:
    static bool cmp(vector<int> a, 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>> res;
        for (int i = 0; i < people.size(); ++i) {
            int pos = people[i][1];
            res.insert(res.begin() + pos, people[i]);
        }
        return res;
    }
};

需要注意,排序规则中,如果两元素身高相等,应该把k小的元素排在前面。否则最后插入时,可能出现插入目标位置靠后的元素先插入,导致插入的目标位置越界而报错。此外,这一题与day 34中的第3题(LeetCode 135. 分发糖果)很相似,都是先按照一个维度做好相应处理,再处理另一个维度,如果同时考虑两者就很难做到。

        二刷:忘记题解方法和insert()用法(vector.insert(vector.begin() + idx, x))。


        第3题(LeetCode 452. 用最少数量的箭引爆气球)自己的思路正确,但实现上因为一个问题而导致超时。首先对气球按照start坐标(按照end排序也可以)排序,贪心策略是对尽可能多气球的重叠区间射出一只箭。所以需要不断更新重叠区间。如果当前气球与重叠区间有重叠,就更新重叠区间的末尾为原本末尾和当前气球end中的最小值;如果当前气球与重叠区间没有重叠,就射出一只箭,并将重叠区间末尾更新为当前气球的end。由于已经排过序,所以重叠区间只需要用末尾来表示即可。

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

而在实现时,因为cmp函数的参数没有用引用类型而导致超时。所以这里获得一个很重要的经验,就是以后题目中的cmp函数也要按照引用类型传参

        题解与自己方法一样,也是看了题解才直到了自己代码超时的原因。

        二刷:有点忘记方法,后来写出来。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值