一、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. 用最少数量的箭引爆气球
状态:已解决
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)的栈空间