Medium草稿★★★★★O(n) 時間 · O(1) 空間
Dynamic ProgrammingString
Patterns🔲 網格・雙序列 DP
尚未複習過

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

926Flip String to Monotone Increasing2-D DPMedium2-D DP

給定一個只包含 '0''1' 的字串 s,你可以將任意字元翻轉(0 變 1 或 1 變 0)。回傳使字串變成單調遞增(所有 0 在所有 1 前面)的最少翻轉次數。

Example:

Input: s = “00110” Output: 1

Intuition

TIP

遍歷時維護兩個狀態:到目前為止以 0 結尾和以 1 結尾的最少翻轉次數。

Approaches

1. Prefix Sum — O(n) / O(n)
  • Idea: 枚舉分割點,分割點左邊所有 1 要翻成 0,右邊所有 0 要翻成 1。用前綴和快速計算。
  • Time: O(n)
  • Space: O(n)
class Solution {
    fun minFlipsMonoIncr(s: String): Int {
        val n = s.length
        val ones = IntArray(n + 1) // ones[i] = s[0..i-1] 中 1 的數量
        for (i in 0 until n) ones[i + 1] = ones[i] + (s[i] - '0')

        var result = n
        for (i in 0..n) {
            val flipOnes = ones[i]           // 左邊的 1 要翻成 0
            val flipZeros = (n - i) - (ones[n] - ones[i]) // 右邊的 0 要翻成 1
            result = minOf(result, flipOnes + flipZeros)
        }
        return result
    }
}
⭐ 2. State DP (O(1) Space) — O(n) / O(1)
  • Idea: 維護 dp0(以 0 結尾的最少翻轉)和 dp1(以 1 結尾的最少翻轉),逐字元更新。
  • Time: O(n)
  • Space: O(1)
class Solution {
    fun minFlipsMonoIncr(s: String): Int {
        var dp0 = 0 // 以 0 結尾的最少翻轉
        var dp1 = 0 // 以 1 結尾的最少翻轉

        for (c in s) {
            if (c == '0') {
                // dp0 不變(0 跟在 0 後面)
                dp1 = minOf(dp0, dp1) + 1 // 把 0 翻成 1
            } else {
                dp1 = minOf(dp0, dp1)     // 1 跟在 0 或 1 後面都可以
                dp0 += 1                   // 把 1 翻成 0
            }
        }
        return minOf(dp0, dp1)
    }
}

🔑 Takeaways