[LeetCode] Permutation in String 字串中的全排列
Given two strings s1 and s2, write a function to return true if s2 contains the permutation of s1. In other words, one of the first string's permutations is the substring of the second string.
Example 1:
Input:s1 = "ab" s2 = "eidbaooo" Output:True Explanation: s2 contains one permutation of s1 ("ba").
Example 2:
Input:s1= "ab" s2 = "eidboaoo" Output: False
Note:
- The input strings only contain lower case letters.
- The length of both given strings is in range [1, 10,000].
這道題給了兩個字串s1和s2,問我們s1的全排列的字串任意一個是否為s2的字串。雖然題目中有全排列的關鍵字,但是跟之前的全排列的題目的解法並不一樣,如果受思維定勢影響比較深的話,很容易遍歷s1所有全排列的情況,然後檢測其是否為s2的子串,這種解法是非常不高效的,估計OJ不會答應。 這道題的正確做法應該是使用滑動視窗Sliding Window的思想來做,可以使用兩個雜湊表來做,或者是使用一個雜湊表配上雙指標來做。我們先來看使用兩個雜湊表來做的情況,我們先來分別統計s1和s2中前n1個字串中各個字元出現的次數,其中n1為字串s1的長度,這樣如果二者字元出現次數的情況完全相同,說明s1和s2中前n1的字元互為全排列關係,那麼符合題意了,直接返回true。如果不是的話,那麼我們遍歷s2之後的字元,對於遍歷到的字元,對應的次數加1,由於視窗的大小限定為了n1,所以每在視窗右側加一個新字元的同時就要在視窗左側去掉一個字元,每次都比較一下兩個雜湊表的情況,如果相等,說明存在,參見程式碼如下:
解法一:
class Solution { public: bool checkInclusion(string s1, string s2) { int n1 = s1.size(), n2 = s2.size(); vector<int> m1(128), m2(128); for (int i = 0; i < n1; ++i) { ++m1[s1[i]]; ++m2[s2[i]]; } if (m1 == m2) return true; for (int i = n1; i < n2; ++i) { ++m2[s2[i]]; --m2[s2[i - n1]]; if (m1 == m2) return true; } return false; } };
下面這種解法是利用一個雜湊表加上雙指標,我們還是先統計s1中字元的出現次數,然後遍歷s2中的字元,對於每個遍歷到的字元,我們在雜湊表中對應的字元次數減1,如果次數次數小於0了,說明該字元在s1中不曾出現,或是出現的次數超過了s1中的對應的字元出現次數,那麼我們此時移動滑動視窗的左邊界,對於移除的字串,雜湊表中對應的次數要加1,如果此時次數不為0,說明該字元不在s1中,繼續向右移,直到更新後的次數為0停止,此時到達的字元是在s1中的。如果次數大於等於0了,我們看此時視窗大小是否為s1的長度,若二者相等,由於此時視窗中的字元都是在s1中存在的字元,而且對應的次數都為0了,說明視窗中的字串和s1互為全排列,返回true即可,參見程式碼如下:
解法二:
class Solution { public: bool checkInclusion(string s1, string s2) { int n1 = s1.size(), n2 = s2.size(), left = 0; vector<int> m(128); for (char c : s1) ++m[c]; for (int right = 0; right < n2; ++right) { if (--m[s2[right]] < 0) { while (++m[s2[left++]] != 0) {} } else if (right - left + 1 == n1) return true; } return n1 == 0; } };
下面這種解法也是用一個雜湊表外加雙指標來做的,跟上面的解法思路大體相同,寫法有些不同,不變的還是統計s1中字元出現的次數,不一樣的是我們用一個變數cnt來表示還需要匹配的s1中的字元的個數,初始化為s1的長度,然後遍歷s2中的字元,如果該字元在雜湊表中存在,說明匹配上了,cnt自減1,雜湊表中的次數也應該自減1,然後如果cnt減為0了,說明s1的字元都匹配上了,如果此時視窗的大小正好為s1的長度,那麼說明找到了s1的全排列,返回true,否則說明視窗過大,裡面有一些非s1中的字元,我們將左邊界右移,同時將移除的字串在雜湊表中的次數自增1,如果增加後的次數大於0了,說明該字元是s1中的字元,我們將其移除了,那麼cnt就要自增1,參見程式碼如下:
解法三:
class Solution { public: bool checkInclusion(string s1, string s2) { int n1 = s1.size(), n2 = s2.size(), cnt = n1, left = 0; vector<int> m(128); for (char c : s1) ++m[c]; for (int right = 0; right < n2; ++right) { if (m[s2[right]]-- > 0) --cnt; while (cnt == 0) { if (right - left + 1 == n1) return true; if (++m[s2[left++]] > 0) ++cnt; } } return false; } };
類似題目:
參考資料: