[劍指-Offer] 48. 最長不含重複字符的子字符串(滑動窗口、哈希映射、常規解法)

1. 題目來源

鏈接:最長不含重複字符的子字符串
來源:LeetCode——《劍指-Offer》專項

2. 題目說明

給定一個字符串,請你找出其中不含有重複字符的 最長子串 的長度。

示例1:

輸入: “abcabcbb”
輸出: 3
解釋: 因爲無重複字符的最長子串是 “abc”,所以其長度爲 3。

示例2:

輸入: “bbbbb”
輸出: 1
解釋: 因爲無重複字符的最長子串是 “b”,所以其長度爲 1。

示例3:

輸入: “pwwkew”
輸出: 3
解釋: 因爲無重複字符的最長子串是 “wke”,所以其長度爲 3。請注意,你的答案必須是 子串 的長度,“pwke” 是一個子序列,不是子串。

提示:

  • s.length <= 40000

3. 題目解析

方法一:滑動窗口+HashMap+常規解法

這道題字符出現的位置很重要,所以可以使用 HashMap 來建立字符和其出現位置之間的映射。主要思路如下:

  • 維護了一個滑動窗口,窗口內的都是沒有重複的字符,需要儘可能的擴大窗口的大小

  • 由於窗口在不停向右滑動,所以只關心每個字符最後出現的位置,並建立映射

  • 窗口的右邊界就是當前遍歷到的字符的位置,爲了求出窗口的大小,需要一個變量 left 來指向滑動窗口的左邊界

  • 如果當前遍歷到的字符從未出現過,那麼直接擴大右邊界

  • 如果之前出現過,那麼就分兩種情況,在或不在滑動窗口內

    • 如果不在,那麼就沒事,當前字符可以加進來
    • 如果在,需要先在滑動窗口內去掉這個已經出現過的字符了,去掉的方法並不需要將左邊界 left 一位一位向右遍歷查找,由於 HashMap 已經保存了該重複字符最後出現的位置,所以直接移動 left 指針就可以了。
  • 維護一個結果 res,每次用出現過的窗口大小來更新結果 res,就可以得到最終結果啦。

注意將 left 初始化爲 -1,在 i - left 時,不必判斷單個字符的情況了。

參見代碼如下:

// 執行用時 :40 ms, 在所有 C++ 提交中擊敗了28.37%的用戶
// 內存消耗 :11.1 MB, 在所有 C++ 提交中擊敗了100.00%的用戶

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if (s.size() == 0) return 0;
        int res = 0, left = -1;
        unordered_map<int, int> m;
        for (int i = 0; i < s.size(); ++i) {
            if (m.count(s[i]) and m[s[i]] > left) left = m[s[i]];
            
            m[s[i]] = i;
            res = max(res, i - left);
        }
        return res;
    }
};

方法二:滑動窗口+vector映射數組+常規解法

下面這種寫法是上面解法的精簡模式,這裏可以建立一個 256 位大小的整型數組來代替 HashMap,這樣做的原因是 ASCII 表共能表示 256 個字符,也是常見的哈希映射初始化方式,但是由於鍵盤只能表示 128 個字符,所以用 128 也行,然後全部初始化爲 -1,這樣的好處是: 不用像之前的 HashMap 一樣要查找當前字符是否存在映射對了,對於每一個遍歷到的字符,直接用其在數組中的值來更新 left因爲默認是 -1,而 left 初始化也是 -1,所以並不會產生錯誤,這樣就省了 if 判斷的步驟,其餘思路都一樣。

參見代碼如下:

// 執行用時 :0 ms, 在所有 C++ 提交中擊敗了100.00%的用戶
// 內存消耗 :10.1 MB, 在所有 C++ 提交中擊敗了100.00%的用戶

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector<int> m(128, -1);
        int res = 0, left = -1;
        for (int i = 0; i < s.size(); ++i) {
            left = max(left, m[s[i]]);
            m[s[i]] = i;
            res = max(res, i - left);
        }
        return res;
    }
};

方法三:滑動窗口+HashSet+常規解法

下面這種解法使用了 HashSet,核心算法和上面的很類似,把出現過的字符都放入 HashSet 中,遇到 HashSet 中沒有的字符就加入 HashSet 中並更新結果 res,如果遇到重複的,則從左邊開始刪字符,直到刪到重複的字符停止。感覺這個的 滑動窗口 更加動感,更加形象。

// 執行用時 :32 ms, 在所有 C++ 提交中擊敗了41.72%的用戶
// 內存消耗 :13.6 MB, 在所有 C++ 提交中擊敗了100.00%的用戶

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int res = 0, left = 0, i = 0, n = s.size();
        unordered_set<char> t;
        while (i < n) {
            if (!t.count(s[i])) {
                t.insert(s[i++]);
                res = max(res, (int)t.size());
            }  
            else {
                t.erase(s[left++]);
            }
        }
        return res;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章