数据结构与算法之贪心: 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)

你可能感兴趣的:(Data,Structure,and,Algorithms,leetcode,算法,贪心)