个人主页:@Sherry的成长之路
学习社区:Sherry的成长之路(个人社区)
专栏链接:练题
长路漫漫浩浩,万事皆有期待
860. 柠檬水找零 - 力扣(LeetCode)
每一杯柠檬水5块钱,能收到的面额只有5,10和20三种金额的钱币。一开始我没有记录怎么找零,将三者都化为了自己得到了多少钱,然后判断是否能找零。后来发现不对劲,必须要记录下来你获得的钱币各面额的都是多少,但是实际上20元的钱我们可以不做记录,因为20块钱的一张纸币,找不了钱,最大人家就给20块钱,而五块钱和十块钱能找开10块钱和20块钱的,我们需要将它们分别记录下来,一共有多少张。如何存钱的思路明确了,就好办了,每次遇到需要找钱的时候我们判断一下,有没有零钱,就可以了。
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int target=0;
map<int,int>hash_map;
for(int i=0;i<bills.size();i++){
if(bills[i]==5)hash_map[5]++;
else if(bills[i]==10)hash_map[10]++;
target=bills[i]-5;
if(target==5){
if(hash_map[5])hash_map[5]--;
else return false;
}
else if(target==15){
if(hash_map[10]&&hash_map[5]){
hash_map[10]--;hash_map[5]--;
}
else if(hash_map[5]>=3){
hash_map[5]-=3;
}
else return false;
}
}
return true;
}
};
这里我用了数组vecor来存储,五元钱和十元钱都获得了几张,并且需要注意的是如果我们碰到20元需要找15的时候,应该尽量使用先看有没有10元纸币的策略,如果没有再找三个五元,因为十元只能找开20元,要合理使用,避免找10元时候5元不够。
当然了,为了提高效率,我们可以在存储纸币有多少的时候,用整形int来代替,也是可以的。
406. 根据身高重建队列 - 力扣(LeetCode)
这道题的排序部分和上一期的分发糖果差不多的思路,上一个题只不过不是那么明确,但是这道题明确的给出了两个不同属性的值,我们要根据两个不同属性的值来对数据进行排序。上一个题是当前分发糖果的孩子可能要同时比较左孩子和右孩子的分数,再进行排序调整,所以理论上也都是两次排序。
思路是这样的,我们先按照身高排序,如果先按照前面有几个数的k排序,是不对的,因为k的值是固定的,题的言外之意是,我们先确定好身高后,排序k看前面有几个数据,而不能先排序k,这样会造成两个属性都没办法合理排序,浪费时间。我们先按照身高进行排序,排降序把身高高的排在数组的前面,然后再按照第二个属性k将数据插入进去,如果两个人身高相等,那么k值低的在前面,高的在后面。
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>>result;
for(int i=0;i<people.size();i++){
int pos=people[i][1];
result.insert(result.begin()+pos,people[i]);
}
return result;
}
};
我们再进行第一个属性的排序时候,先不把数据整个压到数组内部,先对其进行排序,因为什么不像那道分发糖果一样直接放入数组然后用++来调整呢,因为我们要返回的这个数组里面包含多个数组,我们在进行第一步排列时候就将内容加到返回数组里了,在第二次排序调整的时候,数据很难再从结果数组里进行排序了,因为我们每次排序的是一个数组。正确的做法是,第一次按照身高排完序后,第二次排序的时候确定了,再压入数组中,实际上这里的第二次排序是很有讲究的,第一次排序的时候我们已经按照数据排了对身高的降序,这个时候我们再用一个变量来保存它这个当前值的k值,它决定了我们这个数组插入到哪个位置。
我们首先将如果数据一样,k值小的排在了前面,所以一般来说第一个数据就是插在0这个位置上的,以此类推都是根据k值来调整插入的顺序,而为什么要先排序大的数据呢?因为我们先将身高大的数据插进去了,这样我们身高小的数据插入时候,会把身高大的数据向后面挤(当身高小的数据k值也小的时候),身高大的前面有身高小的是没有任何问题的(当身高大的前面有0个人时,或前面有比他大的身高)所以我们才进行这样的排序,如果身高小的数据k值也小的话,那它自然就插入到后面了,这个是很容易理解的。
总的来说,思路听巧妙的,但是用数组做存储并不方便,会增加运行的负担,vector的插入函数运行效率不高,而且vector底层是数组实现,如果要插入数据过大,它要开辟一个两倍于当前大小,然后将数据拷贝进来再操作,如果它总是拷贝会大大影响效率。
我们可以将其改为用链表来操作,将数据用链表连接保存,然后返回时候再化为数组。
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);
list<vector<int>>result;
for(int i=0;i<people.size();i++){
int pos=people[i][1];
list<vector<int>>::iterator it=result.begin();
while(pos--)it++;
result.insert(it,people[i]);
}
return vector<vector<int>>(result.begin(),result.end());
}
};
注意
:这里的it并不是多此一举,c++里的list的insert函数并不是随机迭代访问器,不能像操作数组那样加加减减的,我们需要调整迭代器的位置之后,再进行传入。
452. 用最少数量的箭引爆气球 - 力扣(LeetCode)
贪心算法中的重叠问题,两个气球都有其所在的范围,也就是所谓的直径,将两个气球放在一个x,y的平面上,如果两个气球x部分有重叠,那么一支箭就可以射爆两个气球,题目给出箭从x轴垂直向上射出,可以无限距离的走,也就是说路径上只要有气球都可以射爆,需要注意的一点是,如果两个气球的边界是紧挨着的,那么一支箭从两气球中间穿过去,也就是边界处,也是可以将两个气球同时射爆的。
解题思路:如何判断两气球是否有重叠部分?第二个气球的最左端的坐标如果小于第一个气球的最右端,说明存在重叠部分,即使等于也可以这就是上面所说的紧挨着的情况,但是如果大于了则说明没有重叠部分需要另一只箭,值得注意的是如果有两气球重叠判断第三只气球也是否可以一起射爆的时候,我们需要判断的有可能不再是第一个气球的末尾处,有可能是第二个气球的末尾处,由于前两个气球已经重叠,可以看作一个整体,如果第二个气球过小,完全被第一个气球所包含,那么我们下一次的判断边界应该是两气球中的较小的那一个下标,来缩小其范围,如果这时候还用第一个下标那么就可能出现射爆一三气球,而射不到第二个气球的情况,这一点需要额外注意。
class Solution {
public:
static bool cmp(vector<int>&a,vector<int>&b){
return a[0]<b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
if(points.size()==0)return 0;
int result=1;
sort(points.begin(),points.end(),cmp);
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][1],points[i][1]);
}
return result;
}
};
代码还算简单,但是思路很难在没有做过的时候想出来。
写判断标准的cmp时候,一定要传入数组引用,不要只单独传入数组,因为数组过大的时候,直接传入它需要先对数组整个进行复制传入,很费时。
为什么要先将数组排序后再做处理呢?这是为了方便寻找区间重叠,让可能重叠的区间挨在一起。
今天我们完成了柠檬水找零、根据身高重建队列、用最少数量的箭引爆气球三道题,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~