一. 一般需要用到雙指針, 還有特殊的需要用到特定數據結構sorted_map.
二. 這類題目脫離不開主串(主數組)和子串(子數組)的關係, 短的在長的中滿足一定的條件.要求時間O(n),空間O(1).
三. 雙指針確定一個窗口.
四. 思路是每次保證右指針往前移動一格, 每次移動會有新的元素進入窗口. 條件可能發生改變, 根據這個決定左指針是否移動.
五. 例題
1. leetcode76_最小覆蓋子串
2. 暴力法: 列舉出S所有的子串, 並且判斷子串是否包含有T全部字母,最後選出長度最小的子串
3. 我們的做法是, 先找到可行解,即right指針先走,直到包含所有的T字母, 然後再優化可行解, 移動left指針, 窗口不斷向右滑動, 最終找到最優解.
4. 假設needs 和 window 相當於計數器,分別記錄 T 中字符出現次數和窗口中的相應字符的出現次數。用兩個哈希表來解決.
class Solution {
public:
string minWindow(string s, string t) {
//初始情況
if(t.length()==0 || s.length()<t.length()) return "";
//雙指針
int left=0, right=0;
int match = 0;
map<char, int> need;
map<char, int> window;
//設置沒有匹配的字符串的情況
int start=0, minLength=INT_MAX;
//首先哈希表列出t的字符個數情況.
for(int i=0;i<t.length();i++) {
need[t[i]]++;
}
//然後指定雙指針,right直到s末尾才停止.
while(right<s.length()) {
//如果s的新增的字符在t中,則window對應的字符++,
//如果第一次到了和t字符相同的個數,則match++.
if(need.count(s[right])) {
window[s[right]]++;
if(window[s[right]]==need[s[right]]) {
match++;
}
}
right++;
//當t中字符在s中全部出現時,可行解滿足,
接下來優化可行解.
while(match==need.size()) {
//字符串長度越小則保留.
if(right-left<minLength) {
start = left;
minLength = right-left;
}
//如果移動left時刪除的字符在t中,
//則如果個數達到need臨界,則match--,
//其他情況window對應字符--.
if(need.count(s[left])) {
if(window[s[left]]==need[s[left]]) match--;
window[s[left]]--;
}
left++;
}
//別忘了left++和right++, 雙指針自身也需要移動.
}
return (minLength==INT_MAX) ? "" : s.substr(start, minLength);
}
};
5. 算法時間複雜度O(M+N). 兩個while循環最多執行2M次, 因爲while的次數就是left和right走的路程, 因爲滑動窗口, left和right都是++,即都是前進,因此走的路程最多是2M, 即兩個s字符串的長度.
二. leetcode438_找到字符串中所有字母異位詞.
1. 和上一題思路一樣, 套用模板, 先找到s中包含所有字母的字符串, 然後再去驗證長度是否相同, 再添加進最終的結果.
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> res;
if(s.length()==0 || s.length() < p.length()) return res;
int left=0, right=0;
map<char, int> need;
map<char, int> window;
int match = 0;
for(int i=0; i<p.length(); i++) {
need[p[i]]++;
}
while(right<s.length()) {
//先找到字母個數符合條件的.
if(need.count(s[right])) {
window[s[right]]++;
if(window[s[right]]==need[s[right]]) match++;
}
right++;
//然後再篩選出個數符合要求的添加進最終的結果.
while(match==need.size()) {
if(right-left == p.size()) {
res.push_back(left);
}
if(need.count(s[left])) {
window[s[left]]--;
if(window[s[left]]<need[s[left]]) match--;
}
left++;
}
}
return res;
}
};
2. 時間同樣爲O(N+M).
三. leetcode3_無重複字符的最長子串.
1. 思路沒變,但是代碼結構有些變化.
2. 遇到子串問題, 首先想到的就是滑動窗口技巧.
3. 先移動right, 等出現重複字符的時候, 再向左移動left, 如此往復.
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//初始條件
if(s.length()==0) return 0;
int left = 0, right = 0;
map<char, int> window;
int match = 0;
int res = INT_MIN;
//當滿足沒有重複字符的時候,right指針一直向右移動,
//並且只有字符個數爲1時才match++
while(right < s.length()) {
window[s[right]]++;
if(window[s[right]]==1) match++;
right++;
//當不滿足無重複子串時,需要移動左指針
while(right-left!=match) {
//如果不等於,則說明移動會使無重複子串的長度減1.
if(s[left]!=s[right-1]) {
match--;
}
window[s[left]]--;
left++;
}
//找到最長的子串
res = max(res, match);
}
return res;
}
};