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; } };
類似題目:
參考資料:
https://discuss.leetcode.com/topic/87856/sliding-window-o-n-c
https://discuss.leetcode.com/topic/87845/java-solution-sliding-window
https://discuss.leetcode.com/topic/87861/c-java-clean-code-with-explanation
https://discuss.leetcode.com/topic/87884/8-lines-slide-window-solution-in-java
