Medium草稿★★★★★O(n) 時間 · O(n) 空間
StackMonotonic StackGreedy
Patterns📚 單調堆疊🥞 堆疊🪙 貪心
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

402Remove K DigitsStackMediumStack

給定一個非負整數的字串表示 num 和整數 k,移除 k 個數字使得剩餘數字組成的數最小。回傳該最小數的字串表示。

Example:

Input: num = “1432219”, k = 3 Output: “1219”

Intuition

TIP

核心思路:用單調遞增堆疊,當新數字比棧頂小時就彈出棧頂(相當於移除較大的數字),最多移除 k 個。

Approaches

1. Brute Force: Remove One Drop per Pass — O(n·k) / O(n)
  • Idea: 每輪從左找第一個「比右鄰大」的數字移除(無則移除最後一個),重複 k 輪
  • Time: O(n·k) - k 輪、每輪掃描 O(n)
  • Space: O(n) - 字串重建
class Solution {
    fun removeKdigits(num: String, k: Int): String {
        var s = num
        repeat(k) {
            var i = 0
            while (i < s.length - 1 && s[i] <= s[i + 1]) i++  // 第一個下降點(或末尾)
            s = s.removeRange(i, i + 1)
        }
        return s.trimStart('0').ifEmpty { "0" }
    }
}
⭐ 2. Monotonic Increasing Stack + Greedy — O(n) / O(n)
  • Idea: 遍歷每個數字,用 while 迴圈彈出所有大於當前數字的棧頂(同時遞減 k),最後去除前導零
  • Time: O(n) - 每個數字最多入棧出棧各一次
  • Space: O(n)
class Solution {
    fun removeKdigits(num: String, k: Int): String {
        var remaining = k
        val stack = ArrayDeque<Char>()

        for (digit in num) {
            while (remaining > 0 && stack.isNotEmpty() && stack.last() > digit) {
                stack.removeLast()
                remaining--
            }
            stack.addLast(digit)
        }

        // 若還沒移除夠 k 個(例如遞增序列),從尾端移除
        repeat(remaining) {
            stack.removeLast()
        }

        // 去除前導零
        val result = stack.joinToString("").trimStart('0')

        return if (result.isEmpty()) "0" else result
    }
}

🔑 Takeaways