題目描述:你在賣每杯 5 元的檸檬水,顧客只會付 5 元、10 元或 20 元。初始時你沒有任何零錢,判斷能否給每位顧客正確找零。
解題思路:
這是一道直觀的貪心模擬題。我們只需要追蹤手頭的 5 元和 10 元鈔票數量(20 元不需要追蹤,因為 20 元鈔票無法用於找零)。
三種情況分析:
顧客付 5 元:不需要找零,收入 five++。
顧客付 10 元:需要找回 5 元。若 five > 0,則 five--,ten++;否則回傳 false。
顧客付 20 元:需要找回 15 元。優先用 1 張 10 元 + 1 張 5 元(貪心選擇:保留更多 5 元,因為 5 元用途最廣),若不夠則嘗試 3 張 5 元;兩者都不夠則回傳 false。
為何優先用 10 元找零:10 元鈔票只能用於找零 20 元的情況,而 5 元鈔票可用於找零 10 元和 20 元兩種情況。因此 5 元更珍貴,應儘量保留。
範例說明:
bills = [5, 5, 5, 10, 20]
5 → five = 1
5 → five = 2
5 → five = 3
10 → five = 2, ten = 1(找回 1 個 5 元)
20 → ten = 0, five = 1(找回 1 個 10 + 1 個 5)
結果:true
時間複雜度:O(n) — 對每位顧客(共 n 位)進行一次常數時間的判斷與更新,整體線性掃描一次。
空間複雜度:O(1) — 只使用兩個整數變數 five 和 ten 記錄鈔票數量,不隨輸入規模增長。
函式 lemonadeChange(bills):
1. 設 five = 0,ten = 0
2. 對每個 bill 在 bills 中:
a. 若 bill == 5:five 加 1
b. 若 bill == 10:
- 若 five == 0:回傳 false
- five 減 1,ten 加 1
c. 若 bill == 20:
- 若 ten > 0 且 five > 0:ten 減 1,five 減 1
- 否則若 five >= 3:five 減 3
- 否則:回傳 false
3. 回傳 true用一個 map 記錄各面額鈔票數量,找零時從大面額往小面額貪心扣除。這種方法更具通用性,可以擴展至任意面額組合,但對本題來說過於繁瑣。
map<int, int> cash;
for (int bill : bills) {
cash[bill]++;
int change = bill - 5;
for (auto it = cash.rbegin(); it != cash.rend() && change > 0; it++) {
int use = min(it->second, change / it->first);
it->second -= use;
change -= use * it->first;
}
if (change != 0) return false;
}
return true;
時間複雜度 O(n × k),k 為面額種類數。
注意到 10 元鈔票只在找零 20 元時才用得到,且用 10 元找零一定優先於用 5 元。可以進一步觀察:20 元找零時必然消耗一張 5 元;因此實際上只需記錄 5 元和 10 元的數量,等同於目前的標準解法。這不是進一步的簡化,而是驗證了標準解法已是最精簡的形式。
直接計算任意時刻手頭的零錢總額(只計 5 元和 10 元),若任一時刻無法湊出找零金額則失敗。但這種方法難以判斷具體面額是否夠用,仍需分別追蹤面額,等同於標準解法。
若題目允許任意面額(不只 5/10/20 元),如何設計通用的找零貪心演算法? 需考慮「面額系統是否為正則系統(canonical coin system)」——例如歐美的硬幣系統是正則的,貪心(從大面額到小面額)可得最優解;但某些特殊面額系統(如 1, 3, 4 元)貪心可能不最優,需改用 DP。
若顧客順序可以重新排列,哪種順序最有利於找零成功? 直覺上應先處理付較小面額的顧客(先收 5 元),以累積足夠零錢。請嘗試設計一個排序策略並證明其正確性。
若初始給定一定數量的零錢(例如 k 張 5 元),問題的難度如何變化? 演算法框架相同,只需修改初始值 five = k,但貪心的正確性證明不變。請思考初始零錢數量 k 最少要多少,才能保證對任意合法的顧客序列都能成功找零。