基本資料#
難易度: medium 第一次嘗試: 2025-08-02
- 總花費時間:00:00.00
問題描述#
給定兩個字符串 s 和 p,返回 s 中所有 p 的變位詞的起始索引陣列。變位詞是通過重新排列不同單詞或短語的字母而形成的單詞或短語,通常使用所有原始字母恰好一次。
解題思路#
我的直覺是使用雜湊表來存儲字符的出現次數。在滑動窗口時,我們計算右側的元素並扣除左側的元素。如果出現次數表與目標計數匹配,那就是我們想要的索引,代表目標的變位詞。從論壇中,有一個巧妙的方法使用頻率陣列來存儲字符的計數。通過使用長度為26的陣列,我們可以將所有字符轉換為ASCII序數。在滑動窗口時,我們只需要匹配兩個陣列是否相等,無需刪除計數為零的元素,這節省了時間並消除了冗餘操作。
解法#
方法一:使用 Counter(雜湊表)#
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        len_s = len(s)
        len_p = len(p)
        res = []
        if len(s) < len(p):
            return res
        
        occur = Counter(s[:len_p])
        target = Counter(p)
        if occur == target:
            res.append(0)
        for i in range(len_p, len_s):
            occur[s[i]] += 1
            occur[s[i - len_p]] -= 1
            if occur[s[i - len_p]] == 0:
                del occur[s[i - len_p]]
            if occur == target:
                res.append(i - len_p + 1)
        return res
方法二:使用頻率陣列(更高效)#
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        window_now = [0] * 26
        target = [0] * 26
        res = []
        for c in p:
            target[ord(c) - 97] += 1
        for i in range(len(s)):
            window_now[ord(s[i]) - 97] += 1
            if i - len(p) >= 0:
                window_now[ord(s[i - len(p)]) - 97] -= 1
            if window_now == target:
                res.append(i - len(p) + 1)
        return res
演算法分析#
時間複雜度#
- 方法一: O(n),其中 n 是字符串 s 的長度
- 方法二: O(n),其中 n 是字符串 s 的長度
空間複雜度#
- 方法一: O(k),其中 k 是字符集的大小(最壞情況)
- 方法二: O(1),因為我們使用固定大小的26元素陣列
收穫#
- 滑動窗口與字符計數: 這道題是滑動窗口技巧結合字符頻率計數的經典應用。我們維護一個固定大小的窗口(等於模式p的長度)並追蹤字符頻率。 
- 兩種實現方法: 我探索了兩種不同的方法 - 一種使用Python的Counter類進行雜湊表操作,另一種使用固定大小陣列以獲得更好的性能。陣列方法更高效,因為它避免了字典操作。 
- 基於ASCII的陣列索引: 使用 - ord(c) - 97(其中97是’a’的ASCII值)允許我們將小寫字母映射到陣列索引0-25,使頻率計數非常高效。
- 窗口管理: 關鍵洞察是通過添加新字符和移除舊字符來維持正確的窗口大小。條件 - i - len(p) >= 0確保我們只在窗口完全形成後才開始移除字符。
- 變位詞檢測: 當頻率陣列完全匹配時檢測到變位詞。這比比較排序字符串或使用其他方法更高效。 
- 邊界情況處理: 我們需要處理字符串s比模式p短的情況,在這種情況下不可能存在變位詞。 
- 時間複雜度: O(n),其中n是字符串s的長度,因為我們只需要遍歷字符串一次。 
- 空間複雜度: O(1),因為我們使用固定大小的26元素陣列,無論輸入大小如何。 
遇到的問題#
- 初始雜湊表方法: 我的第一個方法使用Counter對象,雖然有效但由於字典操作效率較低。基於陣列的方法證明要快得多。 
- 窗口邊界邏輯: 我最初對滑動窗口的正確邏輯感到困惑。關鍵是理解我們需要先添加當前字符,然後移除落在窗口外的字符。 
- 索引計算: 變位詞起始索引的計算 - i - len(p) + 1很棘手。我必須仔細思考當前位置與窗口邊界之間的關係。
- 字符頻率管理: 確保當字符被添加到窗口和從窗口中移除時,頻率陣列正確更新需要仔細注意操作的順序。 
- ASCII映射: 最初,我對ASCII映射( - ord(c) - 97)感到困惑。理解小寫字母的ASCII值為97-122有助於澄清為什麼我們減去97來獲得索引0-25。
- 陣列比較: 直接陣列比較 - window_now == target比比較Counter對象更高效,因為它避免了字典開銷。
關鍵要點#
- 滑動窗口技巧: 對於涉及子字符串分析的問題至關重要
- 字符頻率計數: 比較字符分佈的高效方法
- 陣列 vs 雜湊表: 對於已知字符集,固定大小陣列可能比雜湊表更高效
- ASCII操作: 理解ASCII值有助於優化基於字符的演算法

