首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

字串查找經典問題:在母串中尋找子串任意排列的首次出現位置

     編輯:關於C語言

問題描述

給定一個母串 M 和一個子串 S。要求找出 S 的任意排列(即所有字元的全排列) 在 M 中首次出現的起始索引。若不存在,返回 -1。

範例:

M = "qwertyuihgfd"
S = "tyui"
輸出:4  // "tyui" 的排列 "yuit" 等在索引 4 開始處出現

核心洞察:滑動窗口 + 字元頻率匹配

由於 S 的排列數量隨長度呈階乘增長(n!),窮舉所有排列去匹配 M 完全不可行。關鍵觀察:

S 的任意排列在 M 中出現 ⟺ M 中存在一個長度為 |S| 的子串,其字元頻率統計與 S 完全一致。

因此,問題轉化為:在 M 中尋找長度固定為 |S| 的滑動窗口,使窗口內字元頻率表等於 S 的頻率表。

演算法步驟(O(N) 時間,O(Σ) 空間)

  1. 建立目標頻率表:遍歷 S,統計每個字元出現次數 → target[256](或 unordered_map)。
  2. 初始化滑動窗口:取 M 前 |S| 個字元,建立窗口頻率表 window[256]。
  3. 滑動比對:
    • 若 window == target → 返回當前起始索引。
    • 否則,窗口右移一位:window[M[i]]--,window[M[i+|S|]]++,繼續比對。
  4. 遍歷結束未找到 → 返回 -1。

優化:避免每次全表比對

引入 matchCount 變數,記錄「當前窗口中與目標頻率一致的字元種類數」。

  • 初始化時計算 matchCount。
  • 滑動時,僅更新進出字元對應的計數,動態維護 matchCount。
  • 當 matchCount == 目標字元種類數 時,即為匹配成功。比對降為 O(1)。

C++ 參考實現(含優化)

int findPermutation(const string& M, const string& S) {
    if (S.empty()) return 0;
    if (M.size() < S.size()) return -1;

    vector target(256, 0), window(256, 0);
    for (char c : S) target[(unsigned char)c]++;

    int required = 0;
    for (int cnt : target) if (cnt > 0) required++;

    int matchCount = 0;
    // 初始窗口 [0, S.size())
    for (size_t i = 0; i < S.size(); ++i) {
        unsigned char c = M[i];
        if (++window[c] == target[c]) matchCount++;
        else if (window[c] == target[c] + 1) matchCount--; // 超過目標
    }
    if (matchCount == required) return 0;

    // 滑動
    for (size_t i = S.size(); i < M.size(); ++i) {
        unsigned char out = M[i - S.size()];
        unsigned char in  = M[i];

        if (window[out] == target[out]) matchCount--;
        else if (window[out] == target[out] + 1) matchCount++; // 從超標變回標準
        window[out]--;

        if (++window[in] == target[in]) matchCount++;
        else if (window[in] == target[in] + 1) matchCount--;

        if (matchCount == required) return i - S.size() + 1;
    }
    return -1;
}

原文代碼分析與改進點

原文採用 List<Char> 逐字元移除的方式,邏輯正確但效率較低:

  • 每次窗口需 ToCharArray().ToList() 復制子串字元 → O(|S|) 額外開銷。
  • Contains + Remove 為 O(|S|) 操作,總體複雜度達 O(N × |S|²)。
  • 僅返回所有匹配位置,而非首次。

本文提供的滑動窗口 + 頻率表方案,將複雜度降至 O(N + |S|),且空間固定,適合大規模文本處理。

擴展思考

  • Unicode 支持:將 256 擴展為 unordered_map<char32_t, int> 或使用 ICU 庫。
  • 多模式匹配:若需同時查找多個子串的排列,可構建 Aho-Corasick 自動機結合頻率指紋。
  • 最小覆蓋子串:LeetCode 76 題變體,尋找包含 S 所有字元的最短窗口,同樣用滑動窗口解決。

總結

「子串任意排列在母串中出現」本質是定長滑動窗口下的字元頻率完全匹配。拋棄排列枚舉思維,轉而使用頻率表 + 滑動窗口 + 增量比對,即可獲得線性時間解法,是面試與實戰中高頻考點的標準解法。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved