Description

給定一個只包含大寫英文字母的字串 s 和一個整數 k,你最多可以將字串中的 k 個字元替換成任意其他大寫字母。回傳替換後能得到的最長連續相同字元子字串長度。

Example:

Input: s = “AABABBA”, k = 1 Output: 4

Intuition#

視窗內「需要替換的字元數 = 視窗長度 - 最高頻字元次數」,只要這個值不超過 k,視窗就合法。

  • 維護一個滑動視窗,追蹤視窗內每個字元的頻率
  • 如果 windowLen - maxFreq > k,代表需要替換的字元太多,收縮左邊界
  • 關鍵洞察:maxFreq 不需要在收縮時精確遞減,因為只有更大的 maxFreq 才能產生更長的答案

Approaches#

1: Brute Force#

  • 概念: 對每個起點嘗試擴展視窗,計算需要替換的字元數
  • 時間複雜度: O(n²)
  • 空間複雜度: O(1)
Brute Force 程式碼
class Solution {
    fun characterReplacement(s: String, k: Int): Int {
        var result = 0
        for (i in s.indices) {
            val count = IntArray(26)
            var maxFreq = 0
            for (j in i until s.length) {
                count[s[j] - 'A']++
                maxFreq = maxOf(maxFreq, count[s[j] - 'A'])
                if (j - i + 1 - maxFreq <= k) {
                    result = maxOf(result, j - i + 1)
                } else {
                    break
                }
            }
        }
        return result
    }
}

⭐2: Sliding Window#

  • 概念: 維護視窗並追蹤最高頻字元次數,當替換數超過 k 時收縮左邊界
  • 時間複雜度: O(n)
  • 空間複雜度: O(1)(固定 26 個字母)
class Solution {
    fun characterReplacement(s: String, k: Int): Int {
        val count = IntArray(26)
        var left = 0
        var maxFreq = 0
        var result = 0
        for (right in s.indices) {
            count[s[right] - 'A']++
            maxFreq = maxOf(maxFreq, count[s[right] - 'A'])
            if (right - left + 1 - maxFreq > k) {
                count[s[left] - 'A']--
                left++
            }
            result = maxOf(result, right - left + 1)
        }
        return result
    }
}

🔑 Takeaways#

  • Pattern: 可變長度滑動視窗 + 頻率計數
  • 關鍵技巧: 「需要替換的字元數 = 視窗長度 - 最高頻字元次數」這個公式是核心,可應用於類似的「允許 k 次修改」問題