LeetCode 860.柠檬水找零
思路:
本题适合用贪心法。其实说是贪心法,但也是非常符合直觉的东西,即:找零的时候从面额最大的张数开始找,如果面额大的找不开再用面额小的钞票,和我们日常生活中找零方式十分类似,但其实这就是一种贪心策略。贪心法很多情况下都是这种非常符合直觉的东西。
我们可以建一个哈希表来表示每张钞票有多少张,如果bills[0]不等于5,则说明我们一张都找不开,因为我们一开始是没有任何零钱的,所以直接返回false。然后从下标1开始遍历bills,每次遍历先扣除5块,剩余的就是要找的零钱。当找零可以被10整除时并且我们还有10块可以找的时候,先找零10块的,否则再找零5块。如果找零不为0但我们没有零钱可以找了则返回false。
解决这道题目的关键思路就是先从10块的开始找,剩下的就是模拟找零的过程了。贪心法没有什么技巧在里面,大多数情况都是根据经验或者直觉,觉得这样找是最优的并且找不出反例,那么总体就是最优的。
代码:
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
vector<int> money(21,0);
if (bills[0] != 5)
return false;
money[5]++;
for (int i = 1; i < bills.size(); i++)
{
int curBill = bills[i];
money[curBill]++;
curBill -= 5;
while(curBill != 0)
{
if (curBill % 10 == 0)
{
if (money[10] > 0)
{
money[10]--;
curBill -= 10;
}
else if (money[5] > 0)
{
money[5]--;
curBill -= 5;
}
else
return false;
}
else if (curBill % 5 == 0)
{
if (money[5] > 0)
{
money[5]--;
curBill -= 5;
}
else
return false;
}
}
}
return true;
}
};
LeetCode 406.根据身高重建队列
思路:
这道题也有一定的难度,光是看题目就挺绕的了。首先要正确理解题意,每个人都有两个维度,一个是自身身高,另一个值代表的含义为身高高于或等于自己的人恰好有多少个站在自己前面,所以很显然的是,首先要给队列排序。
那么到底按照什么维度排序呢?是按身高排序还是按照比自己高的人的数量排序?实际考虑一下就能知道,如果按照比自己高的人的数量排序,排序后的数组给不了我们任何信息,我们需要利用身高这个信息,所以要按照身高来排序。同时,第二个维度表示的是比自己高的人的数量,所以不妨按照从高到矮排序,这样在遍历排序后的数组的时候,可以保证前面的人身高一定高于或等于之后的人。保证了这一点,就可以按照第二个维度的值为下标,往vector里插入数值了,这样插入后的位置一定满足前面正好有那么多个人是比自己高的。
代码:
class Solution {
public:
static bool cmp(const vector<int>& lhs, const vector<int>& rhs)
{
if (lhs[0] == rhs[0])
return lhs[1] < rhs[1] ? true:false;
return lhs[0] > rhs[0] ? true:false;
}
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++)
que.insert(que.begin() + people[i][1], people[i]);
return que;
}
};
LeetCode 452. 用最少数量的箭引爆气球
思路:
这道题目的本质是寻找不重叠区间的数量,如果两个气球所在的区间有重叠,就可以用一支箭引爆,所以最少引爆气球的箭的数量就等于不重叠区间的数量。那么如何找到不重叠区间的数量?比较简单的方法是把所有区间都按照一种排序方式排列,可以方便比较两个区间是否有重叠的部分。一个区间有两个值,分别代表了区间的最左边和最右边,所以我们用左区间排序还是用右区间排序?其实都可以,只不过遍历的顺序不一样,用左区间的话则是从小到大排序,把所有区间都向左靠,判断重叠的方法则是如果上一个区间的右区间小于下一个区间的左区间,则两个区间有重叠。
我们用一个变量rightmost表示一支箭所能涵盖的区间的最右边的范围,换句话说就是如果下一个区间的左区间超过了rightmost这个值,那么就需要另一支箭了。所以rightmost一定是所有所能涵盖的区间内最小的右区间,因为如果超过了这个范围,拥有最小右区间的那个区间就不在范围内了,所以箭的范围每次都需要取的右区间长度。
// 如果在范围内,箭的范围为最小的右边的长度
rightmost = min(rightmost, points[i][1]);
反之,如果下一个区间的左区间超过了rightmost,就需要一支新的箭,并给新的箭赋上下一个区间的右区间范围。
// 如果不在箭的范围内,更新箭的范围并增加箭的数量
rightmost = points[i][1];
count++;
这样一来就可以算出重复区间的数量,保证所需要的箭的数量最少。
代码:
class Solution {
public:
static bool cmp(const vector<int>& lhs, const vector<int>& rhs)
{
return lhs[0] < rhs[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(), points.end(), cmp);
int count = 1;
// 初始箭位置
int rightmost = points[0][1];
for (int i = 1; i < points.size(); i++)
{
// 如果不在箭的范围内,更新箭的范围并增加箭的数量
if (rightmost < points[i][0])
{
rightmost = points[i][1];
count++;
}
else
// 如果在范围内,箭的范围为最小的右边的长度
rightmost = min(rightmost, points[i][1]);
}
return count;
}
};