↓快轉到主要內容
  1. LeetCode/

LeetCode 420: Strong Password Checker

·2 分鐘· ·
LeetCode Hard Greedy String
Wei Yi Chung
作者
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
目錄

基本資料
#

難易度: hard 第一次嘗試:2026-09-02

  • 提交紀錄:Wrong Answer → Runtime Error ×3 → Wrong Answer → Accepted

解題思路
#

三個條件:長度 6 到 20、包含小寫大寫數字、沒有連續三個相同字元。可以插入、刪除、取代。

先算出兩個量:

  • missing:缺幾種字元類型,0 到 3。
  • 每段連續相同字元的長度 L(只看 L >= 3 的)。一段長度 L 的重複,用取代要 L // 3 次。

接著依長度分三種情況:

1. 長度 < 6:一定要插入 6 - n 個字元。插入的字元可以同時補缺少的類型、也能打斷重複,所以答案是 max(6 - n, missing)。

2. 長度 6 到 20:不需要插入或刪除。每次取代可以同時打斷重複和補類型,答案是 max(取代次數, missing)。

3. 長度 > 20:一定要刪除 n - 20 個。刪除也能減少需要的取代次數,關鍵是刪在哪裡最划算:

  • 長度 L % 3 == 0 的段:刪 1 個就能少 1 次取代(例如 3 → 2,取代從 1 次變 0 次)。
  • L % 3 == 1 的段:刪 2 個少 1 次取代。
  • 其他:每刪 3 個少 1 次取代。

所以刪除的額度依序用在這三種,最後答案是 (n - 20) + max(剩下的取代次數, missing)。

我的嘗試
#

第一版:模型就錯了
#

用 stack 數重複、再拿一個公式組合,沒有區分長度的三種情況,也沒考慮刪除可以抵銷取代。

Runtime Error ×3:打錯變數名稱
#

改成分情況討論後,連續三次 RE:

  1. 用了還沒定義的 remainDelete。
  2. 改成 remainDelete = maxDelete,下一行又打錯成 missingType。
  3. max(replaceCount + missingTypes):max 只給了一個 int。

Wrong Answer:剩餘刪除額度算錯
#

remainDelete = maxDelete              # 應該是 deleteCount - maxDelete

用掉 maxDelete 之後,剩下的額度是「總刪除數減掉用掉的」,不是「用掉的數量」。

正確解法
#

class Solution:
    def strongPasswordChecker(self, password: str) -> int:
        n = len(password)
        missing = 3 - (any(c.islower() for c in password)
                       + any(c.isupper() for c in password)
                       + any(c.isdigit() for c in password))

        repeats = []
        i = 0
        while i < n:
            j = i
            while j < n and password[j] == password[i]:
                j += 1
            if j - i >= 3:
                repeats.append(j - i)
            i = j

        if n < 6:
            return max(6 - n, missing)

        replace = sum(L // 3 for L in repeats)
        if n <= 20:
            return max(replace, missing)

        delete = n - 20
        remain = delete
        mod0 = sum(1 for L in repeats if L % 3 == 0)
        mod1 = sum(1 for L in repeats if L % 3 == 1)

        used = min(remain, mod0)              # 每段刪 1 個,少 1 次取代
        replace -= used
        remain -= used

        used = min(remain, mod1 * 2)          # 每段刪 2 個,少 1 次取代
        replace -= used // 2
        remain -= used

        replace -= remain // 3                # 其餘每刪 3 個,少 1 次取代
        return delete + max(replace, missing)

replace 可能被扣到負數,但最後跟 missing(至少 0)取 max,不影響答案。

複雜度
#

時間 O(n),空間 O(n)。

要特別避免的錯誤
#

  • 沒有分長度三種情況就想用一條公式解決。
  • 刪除時沒有依 L % 3 的優先順序分配。
  • 剩餘額度寫成「用掉的數量」。
  • 改了變數名稱後沒有檢查所有用到的地方。

面試口說整理
#

我先算缺少幾種字元類型,以及每段長度至少 3 的重複段。長度不足 6 時,插入能同時補類型和打斷重複,答案是 max(6 - n, missing);長度在 6 到 20 之間,答案是 max(取代次數, missing)。超過 20 時一定要刪 n - 20 個,而刪除能減少取代:優先刪 L % 3 == 0 的段(刪 1 省 1 次),其次 L % 3 == 1(刪 2 省 1 次),其餘每刪 3 省 1 次。最後加上刪除數和 max(剩下的取代, missing)。時間 O(n)。

相關文章

LeetCode 2193: Minimum Number of Moves to Make Palindrome
·1 分鐘
LeetCode Hard Two-Pointers Greedy String Binary-Indexed-Tree
LeetCode 2193 解題筆記:貪心處理最外層:拿最後一個字元,把它最左邊的同伴換到最前面,花費是那個位置;落單的字元最後在正中間,花費是它到中間的距離
LeetCode 3816: Lexicographically Smallest String After Deleting Duplicate Characters
·1 分鐘
LeetCode Hard Stack Monotonic-Stack Greedy String
LeetCode 3816 解題筆記:單調棧,頂端比較大而且還有別的副本就刪;最後把尾巴重複的也刪掉,因為前綴一定比較小
LeetCode 1147: Longest Chunked Palindrome Decomposition
·1 分鐘
LeetCode Hard Dynamic-Programming Greedy Two-Pointers Rolling-Hash String
LeetCode 1147 解題筆記:從兩端同時往內累積字串,左右一相等就切成一對;越早切越好,剩下的中間(如果有)算一塊
LeetCode 4026: Maximum Gap Between Stations
·2 分鐘
LeetCode Medium Two-Pointers Greedy String
LeetCode 4026 解題筆記:從左邊貪心配出每個工人最早可用的站、從右邊配出最晚可用的站;在第 i 和 i+1 個工人之間切開,最大間隔是 latest[i+1] - earliest[i]
LeetCode 2141: Maximum Running Time of N Computers
·1 分鐘
LeetCode Hard Greedy Binary-Search Sorting
LeetCode 2141 解題筆記:比平均時間還大的電池只能給一台電腦用,連同那台電腦一起拿掉,剩下的取平均;要用嚴格的大於
LeetCode 726: Number of Atoms
·2 分鐘
LeetCode Hard Stack String Hash-Map Sorting
LeetCode 726 解題筆記:先把字串切成元素、數字、括號三種 token,括號用 stack 處理,沒有數字時倍數是 1