数据结构与算法之贪心: LeetCode 860. 柠檬水找零 (Typescript版)
柠檬水找零
- https://leetcode.cn/problems/lemonade-change/
描述
-
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
-
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
-
注意,一开始你手头没有任何零钱。
-
给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
示例 1
输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
示例 2
输入:bills = [5,5,10,10,20]
输出:false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false。
提示
- 1 <= bills.length <= 1 0 5 10^5 105
- bills[i] 不是 5 就是 10 或是 20
算法实现
1 )一般思路
function lemonadeChange(bills: number[]): boolean {
// 表示自己的钱箱(用于存储零钱)
const box: number[] = [];
// 判断是否有顾客还在
while (bills.length) {
// 取出当前排在最前面顾客的钱
const money: number = bills.shift();
// 这种情况不需要找零
if (money === 5) {
box.push(money);
} else {
// 手里的零钱要降序排列也就说最大的面值的钱放在最前面
box.sort((a, b) => b - a);
// 顾客的钱减去饮料的钱就是需要找给顾客的零钱
let change = money - 5;
for (let i = 0, len = box.length; i < len; i++) {
if (box[i] <= change) {
change -= box[i];
box.splice(i, 1);
// 删除了元素,数组的长度发生了变化,要维持刚才的i不变
i--;
}
if (change === 0) break;
}
// 没有足够的零钱找给顾客
if (change !== 0) return false;
// 顾客的钱存起来
box.push(money);
}
}
return true;
};
- 这种贪心策略,没有任何技巧
- 这种算法耗时很严重,时间复杂度 O( n 2 l o g n n^2logn n2logn)
- 上面这种思路就很复杂了,不推荐这种算法
2 )分类讨论
function lemonadeChange(bills: number[]): boolean {
// 准备 5元,10元面值的钱,用于标识是否有对应的零钱
let five: number = 0, ten: number = 0;
// 遍历所有的客户付款
for (const bill of bills) {
// 匹配客户付款5元的场景: 不用找零
if (bill === 5) {
five += 1;
// 匹配客户付款10元的场景: 找零5元,进账10元
} else if (bill === 10) {
if (five === 0) return false;
five -= 1;
ten += 1;
// 匹配客户付款20元的场景: 找零15,进账20(不用处理, 这20元无法用于找零), 有两种情况
} else {
// 情况1: 5元和10元的组合
if (five > 0 && ten > 0) {
five -= 1;
ten -= 1;
// 情况2: 没有10元的情况下, 使用三张5元的
} else if (five >= 3) {
five -= 3;
// 否则找零失败,返回 false
} else return false;
}
}
return true;
};
- 这种是官方提供思路
- 由于顾客只可能给你三个面值的钞票,而且我们一开始没有任何钞票
- 因此我们拥有的钞票面值只可能是 5 美元,10 美元和 20 美元三种
- 我们所能找零的面值也就只有 5 美元 和 10美元 两种面值的组合
- 基于分类讨论的方式,列举了可能得找零场景,一个循环就搞定
- 时间复杂度:O(N),其中 N 是 bills 的长度
- 空间复杂度:O(1),常数空间,只用了2个变量,所以是O(1)
-
- 备注: O(2)也用O(1)来表示,常数都是O(1)