標籤:
leetcode中有幾道題使用同一個思路,大致是先維護一個視窗,每次只移動視窗左側或者右側的邊界,然後針對這個視窗內的元素進行處理。這種方式使用兩個指標,可以將問題的已耗用時間降到O(n)內。
Longest Substring Without Repeating Characters:https://leetcode.com/problems/longest-substring-without-repeating-characters/
這道題我們維護一個視窗,每次將右邊的視窗向前移動一位。另外維護一個hash表,標記當前視窗中的字元的位置,一旦右邊的視窗遇到了當前視窗中的字元,說明有重複,將左邊視窗移到當前視窗中重複字元的下一位,更新重複字元的位置為有視窗的位置。每次移動右視窗都更新當前的最大長度,知道有視窗移到最後一位。
這裡hash table有一個對於字串常用的用法,就是聲明一個長度為256的數組,正好對應256個字元。
1 class Solution { 2 public: 3 int lengthOfLongestSubstring(string s) { 4 vector<int> visit(256, -1); 5 int start = 0, end = 0, len = 0; 6 while(end < s.length()) { 7 if(visit[s[end]] >= start) { 8 start = visit[s[end]] + 1; 9 }10 visit[s[end]] = end;11 len = max(end + 1 - start, len);12 end++;13 }14 return len;15 }16 };View Code
substring with concatenation of all words leetcode:https://leetcode.com/problems/substring-with-concatenation-of-all-words/
這道題的思路也是維護一個視窗和hash表。但是由於這道題的特殊性,需要維護兩個hash,一個是不能改變的hash,用來儲存字典;另一個是會變化的,都用來儲存當前的視窗中訪問的word以及每個word訪問的次數。
為了是演算法在O(n)時間內運行,這裡還有一個技巧,(這個技巧也是使得這道題更難的原因),假設源字串的長度為n,字典中單詞的長度為wlen。因為不是一個字元,所以我們需要對源字串所有長度為wlen的子串進行判斷。做法是i從0到wlen-1個字元開始,得到初始考察的end為i, i+wlen, i+2*wlen, ...的長度為wlen的單詞。這樣就可以保證判斷到所有的滿足條件的串。因為每次掃描的時間複雜度是O(2*n/wlen)(每個單詞不會被訪問多於兩次,一次是視窗右端,一次是視窗左端),總共掃描l次(i=0, ..., wlen-1),所以總複雜度是O(2*n/l*l)=O(n),是一個線性演算法。
1 class Solution { 2 public: 3 vector<int> findSubstring(string s, vector<string>& words) { 4 unordered_map<string, int> stable_dict; 5 for(int i = 0; i < words.size(); i++) { 6 if(stable_dict.find(words[i]) == stable_dict.end()) 7 stable_dict.insert(make_pair(words[i], 1)); 8 else 9 stable_dict[words[i]]++;10 }11 vector<int> res;12 int wlen = words[0].size();13 int i = 0;14 while(i < wlen) {15 int end = i;16 int start = i;17 int count = 0;18 unordered_map<string, int> dict;19 while(end < s.length()) {20 string cur = s.substr(end, wlen);21 string start_cur = s.substr(start, wlen);22 if(stable_dict.find(cur) != stable_dict.end()) {23 if(dict.find(cur) == dict.end())24 dict.insert(make_pair(cur, 0));25 if(dict[cur] < stable_dict[cur]) {26 dict[cur]++;27 count++;28 end += wlen;29 }30 else {31 if(count == words.size()) 32 res.push_back(start);33 dict[start_cur]--;34 count--;35 start += wlen;36 }37 }38 else {39 end += wlen;40 count = 0;41 start = end;42 dict.clear();43 }44 }45 i++;46 }47 return res;48 }49 };View Code
Minimum Window Substring: https://leetcode.com/problems/minimum-window-substring/
這道題的思路和上一道很相似。區別是每次迴圈都要右移右指標,而移動左指標的條件是當所有的字典中的字元都在視窗中,停止移動的條件是有字元不在視窗中。這裡要注意的一點是由於我們採取了紀錄所有出現在視窗中的字典中字元的個數,所以如果某個字元的這個個數比字典中應有的個數多,也應當讓左指標移動,並且減少這個值直到和字典的字元數一樣多。
1 class Solution { 2 public: 3 string minWindow(string s, string t) { 4 string res = ""; 5 int start = 0, end = 0; 6 int count = 0; 7 vector<int> record(256, 0); 8 vector<int> dict(256, 0); 9 for(int i = 0; i < t.length(); i++) {10 dict[t[i]]++;11 }12 while(end < s.length()) {13 if(dict[s[end]] > 0) {14 record[s[end]]++;15 if(record[s[end]] <= dict[s[end]]) {16 count++;17 }18 if(count == t.length()) {19 while(dict[s[start]] == 0 || record[s[start]] > dict[s[start]]) {20 record[s[start]]--;21 start++;22 }23 res = res.length() == 0 ? s.substr(start, end + 1 - start) : res.length() < (end + 1 - start) ? res : s.substr(start, end + 1 - start);24 record[s[start]]--;25 count--;26 start++;27 }28 }29 end++;30 }31 return res;32 }33 };View Code
可以看出這類視窗的問題其實是雙指標掃描,關鍵點是判斷兩個指標的移動條件以及使用hash table儲存視窗資訊。
leetcode中幾道與維護視窗相關的問題