柠檬水找零
由于付账的bills数组是有顺序的,当顾客支付5美元时,我们收下5美元,当顾客支付10美元时,我们返还5美元,当顾客支付20美元时,我们可以返还一张10美元、1张5美元或者三张5美元,因此,我们需要2个变量存储拥有的5美元和10美元的数量,并根据bills数组进行变量的改变,当变量存在负值时,说明找零失败,return false。
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
// 初始化两个计数器,分别用于记录5美元和10美元钞票的数量。
int five = 0;
int ten = 0;
// 遍历顾客支付的钞票序列。
for (int i = 0; i < bills.size(); i++) {
// 如果顾客支付5美元,增加5美元钞票的计数器。
if (bills[i] == 5) {
five++;
// 继续处理下一个钞票,不进行额外的操作。
continue;
}
// 如果顾客支付10美元,增加10美元钞票的计数器,并减少5美元钞票的计数器。
if (bills[i] == 10 && five != 0) {
ten++;
five--;
// 继续处理下一个钞票,不进行额外的操作。
continue;
}
// 如果顾客支付20美元,需要提供正确的零钱。
if (bills[i] == 20) {
// 如果10美元钞票和5美元钞票都有,则提供10美元和5美元的零钱。
if (ten != 0 && five != 0) {
ten--;
five--;
// 继续处理下一个钞票,不进行额外的操作。
continue;
}
// 如果只有5美元钞票,但数量足够(至少3张),则提供3张5美元的零钱。
else if (ten == 0 && five >= 3) {
five -= 3;
// 继续处理下一个钞票,不进行额外的操作。
continue;
}
}
// 如果无法提供正确的零钱,返回 false。
return false;
}
// 如果能够处理所有钞票,返回 true。
return true;
}
};
算法的时间复杂度为O(n),遍历一次bills数组。
空间复杂度为O(1),只需维护2个计数器。
根据身高重建队列
本题的思路直接参考的代码随想录的思路,代码随想录 (programmercarl.com),数组有两个维度,高度h和比他高的人数k,和之前分发糖果类似,这种情况下做贪心算法如果两头兼顾可能顾此失彼,因此,考虑对两者之一先进行排序,对h和k排序,如果按照k来从小到大排序,k的排列结果不正确的同时,身高h的结果也不正确,因此考虑按照身高h进行排序,让身高h从大到小排序(且若身高相同时,k较小的站在前面),这样,身高的顺序就确认了,然后以k为下标重新插入队列就完成了队列的重建,具体参考代码随想录的内容。
class Solution {
public:
static bool cmp(const vector<int>& a, const 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>> ans;
// 遍历排序后的 people 数组。
for (int i = 0; i < people.size(); i++) {
// 获取当前人的身高索引。
int position = people[i][1];
// 将当前人插入到 ans 数组中,位置是他们的索引。
// 这样可以保持每个人在原始队列中的相对顺序。
ans.insert(ans.begin() + position, people[i]);
}
// 返回重构后的队列。
return ans;
}
};
这个算法的时间复杂度为O(nlogn + n^2),一次sort快排O(nlogn),在ans数组中插入元素,由于vector数组的底层扩容机制,以及需要插入再拷贝,算法的总体时间复杂度为O(n^2)。
空间复杂度为O(n)。
使用链表 list,算法时间复杂度和空间复杂度同上,但用时会少些。
// 版本二,使用list(链表)
class Solution {
public:
// 身高从大到小排(身高相同k小的站前面)
static bool cmp(const vector<int> a, const 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>> que; // list底层是链表实现,插入效率比vector高的多
for (int i = 0; i < people.size(); i++) {
int position = people[i][1]; // 插入到下标为position的位置
std::list<vector<int>>::iterator it = que.begin();
while (position--) { // 寻找在插入位置
it++;
}
que.insert(it, people[i]);
}
return vector<vector<int>>(que.begin(), que.end());
}
};
用最少的箭引爆气球
本题和后面的无重叠区间有异曲同工之处,本质上就是找无重叠区间的个数,在重叠区间内的气球可以一箭引爆。
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b) {
return a[0] < b[0];
}
int findMinArrowShots(vector<vector<int>>& points) {
// 如果 points 数组为空,则不需要任何箭,返回 0。
if (points.size() == 0) return 0;
// 使用 sort 函数和 cmp 比较函数对 points 进行排序。
// 排序的目的是将点按照结束位置从左到右排列。
sort(points.begin(), points.end(), cmp);
// 初始化箭的数量为 1,因为至少需要一支箭来覆盖第一个点。
int arrowcount = 1;
// 遍历排序后的 points 数组,从第二个点开始。
for (int i = 1; i < points.size(); i++) {
// 如果当前点的开始位置大于前一个点的结束位置,
// 则需要一支新的箭来覆盖当前点。
if (points[i][0] > points[i - 1][1]) {
arrowcount++;
}
// 如果当前点的开始位置小于或等于前一个点的结束位置,
// 则可以利用前一个点的箭来覆盖当前点。
// 将当前点的结束位置更新为前一个点和当前点结束位置的最小值。
else {
points[i][1] = min(points[i - 1][1], points[i][1]);
}
}
// 返回最少需要的箭的数量。
return arrowcount;
}
};
算法的时间复杂度为O(nlogn),遍历一次,快速排序一次。
空间复杂度为O(n),快速排序需要的空间。

被折叠的 条评论
为什么被折叠?



