给定两个字符串 s
和 ** **p
,找到 s
** ** 中所有 p
的 **变位词 **的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
变位词 指字母相同,但排列不同的字符串。
示例 1:
**输入:** s = "cbaebabacd", p = "abc"
**输出:** [0,6]
**解释:**
起始索引等于 0 的子串是 "cba", 它是 "abc" 的变位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的变位词。
** 示例 2:**
**输入:** s = "abab", p = "ab"
**输出:** [0,1,2]
**解释:**
起始索引等于 0 的子串是 "ab", 它是 "ab" 的变位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的变位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的变位词。
提示:
1 <= s.length, p.length <= 3 * 104
s
和 p
仅包含小写字母
注意:本题与主站 438 题相同: <https://leetcode-cn.com/problems/find-all-anagrams-in-a- string/>
方法一:滑动窗口 思路
根据题目要求,我们需要在字符串 s 寻找字符串 p 的变位词。因为字符串 p 的变位词的长度一定与字符串 p 的长度相同,所以我们可以在字符串 s 中构造一个长度为与字符串 p 的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的变位词。
算法
在算法的实现中,我们可以使用数组来存储字符串 p 和滑动窗口中每种字母的数量。
细节
当字符串 s 的长度小于字符串 p 的长度时,字符串 s 中一定不存在字符串 p 的变位词。但是因为字符串 s 中无法构造长度与字符串 p 的长度相同的窗口,所以这种情况需要单独处理。
代码
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution : def findAnagrams (self, s: str , p: str ) -> List [int ]: s_len, p_len = len (s), len (p) if s_len < p_len: return [] ans = [] s_count = [0 ] * 26 p_count = [0 ] * 26 for i in range (p_len): s_count[ord (s[i]) - 97 ] += 1 p_count[ord (p[i]) - 97 ] += 1 if s_count == p_count: ans.append(0 ) for i in range (s_len - p_len): s_count[ord (s[i]) - 97 ] -= 1 s_count[ord (s[i + p_len]) - 97 ] += 1 if s_count == p_count: ans.append(i + 1 ) return ans
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public List<Integer> findAnagrams (String s, String p) { int sLen = s.length(), pLen = p.length(); if (sLen < pLen) { return new ArrayList <Integer>(); } List<Integer> ans = new ArrayList <Integer>(); int [] sCount = new int [26 ]; int [] pCount = new int [26 ]; for (int i = 0 ; i < pLen; ++i) { ++sCount[s.charAt(i) - 'a' ]; ++pCount[p.charAt(i) - 'a' ]; } if (Arrays.equals(sCount, pCount)) { ans.add(0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { --sCount[s.charAt(i) - 'a' ]; ++sCount[s.charAt(i + pLen) - 'a' ]; if (Arrays.equals(sCount, pCount)) { ans.add(i + 1 ); } } return ans; } }
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Solution { public IList<int > FindAnagrams (string s, string p ) { int sLen = s.Length, pLen = p.Length; if (sLen < pLen) { return new List<int >(); } IList<int > ans = new List<int >(); int [] sCount = new int [26 ]; int [] pCount = new int [26 ]; for (int i = 0 ; i < pLen; ++i) { ++sCount[s[i] - 'a' ]; ++pCount[p[i] - 'a' ]; } if (Enumerable.SequenceEqual(sCount, pCount)) { ans.Add(0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { --sCount[s[i] - 'a' ]; ++sCount[s[i + pLen] - 'a' ]; if (Enumerable.SequenceEqual(sCount, pCount)) { ans.Add(i + 1 ); } } return ans; } }
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution {public : vector<int > findAnagrams (string s, string p) { int sLen = s.size (), pLen = p.size (); if (sLen < pLen) { return vector <int >(); } vector<int > ans; vector<int > sCount (26 ) ; vector<int > pCount (26 ) ; for (int i = 0 ; i < pLen; ++i) { ++sCount[s[i] - 'a' ]; ++pCount[p[i] - 'a' ]; } if (sCount == pCount) { ans.emplace_back (0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { --sCount[s[i] - 'a' ]; ++sCount[s[i + pLen] - 'a' ]; if (sCount == pCount) { ans.emplace_back (i + 1 ); } } return ans; } };
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func findAnagrams (s, p string ) (ans []int ) { sLen, pLen := len (s), len (p) if sLen < pLen { return } var sCount, pCount [26 ]int for i, ch := range p { sCount[s[i]-'a' ]++ pCount[ch-'a' ]++ } if sCount == pCount { ans = append (ans, 0 ) } for i, ch := range s[:sLen-pLen] { sCount[ch-'a' ]-- sCount[s[i+pLen]-'a' ]++ if sCount == pCount { ans = append (ans, i+1 ) } } return }
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 var findAnagrams = function (s, p ) { const sLen = s.length , pLen = p.length ; if (sLen < pLen) { return []; } const ans = []; const sCount = new Array (26 ).fill (0 ); const pCount = new Array (26 ).fill (0 ); for (let i = 0 ; i < pLen; ++i) { ++sCount[s[i].charCodeAt () - 'a' .charCodeAt ()]; ++pCount[p[i].charCodeAt () - 'a' .charCodeAt ()]; } if (sCount.toString () === pCount.toString ()) { ans.push (0 ); } for (let i = 0 ; i < sLen - pLen; ++i) { --sCount[s[i].charCodeAt () - 'a' .charCodeAt ()]; ++sCount[s[i + pLen].charCodeAt () - 'a' .charCodeAt ()]; if (sCount.toString () === pCount.toString ()) { ans.push (i + 1 ); } } return ans; };
复杂度分析
时间复杂度:O(m + (n-m) \times \Sigma),其中 n 为字符串 s 的长度,m 为字符串 p 的长度,\Sigma 为所有可能的字符数。我们需要 O(m) 来统计字符串 p 中每种字母的数量;需要 O(m) 来初始化滑动窗口;需要判断 n-m+1 个滑动窗口中每种字母的数量是否与字符串 p 中每种字母的数量相同,每次判断需要 O(\Sigma) 。因为 s 和 p 仅包含小写字母,所以 \Sigma = 26。
空间复杂度:O(\Sigma)。用于存储字符串 p 和滑动窗口中每种字母的数量。
方法二:优化的滑动窗口 思路和算法
在方法一的基础上,我们不再分别统计滑动窗口和字符串 p 中每种字母的数量,而是统计滑动窗口和字符串 p 中每种字母数量的差;并引入变量 differ 来记录当前窗口与字符串 p 中数量不同的字母的个数,并在滑动窗口的过程中维护它。
在判断滑动窗口中每种字母的数量与字符串 p 中每种字母的数量是否相同时,只需要判断 differ 是否为零即可。
代码
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution : def findAnagrams (self, s: str , p: str ) -> List [int ]: s_len, p_len = len (s), len (p) if s_len < p_len: return [] ans = [] count = [0 ] * 26 for i in range (p_len): count[ord (s[i]) - 97 ] += 1 count[ord (p[i]) - 97 ] -= 1 differ = [c != 0 for c in count].count(True ) if differ == 0 : ans.append(0 ) for i in range (s_len - p_len): if count[ord (s[i]) - 97 ] == 1 : differ -= 1 elif count[ord (s[i]) - 97 ] == 0 : differ += 1 count[ord (s[i]) - 97 ] -= 1 if count[ord (s[i + p_len]) - 97 ] == -1 : differ -= 1 elif count[ord (s[i + p_len]) - 97 ] == 0 : differ += 1 count[ord (s[i + p_len]) - 97 ] += 1 if differ == 0 : ans.append(i + 1 ) return ans
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class Solution { public List<Integer> findAnagrams (String s, String p) { int sLen = s.length(), pLen = p.length(); if (sLen < pLen) { return new ArrayList <Integer>(); } List<Integer> ans = new ArrayList <Integer>(); int [] count = new int [26 ]; for (int i = 0 ; i < pLen; ++i) { ++count[s.charAt(i) - 'a' ]; --count[p.charAt(i) - 'a' ]; } int differ = 0 ; for (int j = 0 ; j < 26 ; ++j) { if (count[j] != 0 ) { ++differ; } } if (differ == 0 ) { ans.add(0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { if (count[s.charAt(i) - 'a' ] == 1 ) { --differ; } else if (count[s.charAt(i) - 'a' ] == 0 ) { ++differ; } --count[s.charAt(i) - 'a' ]; if (count[s.charAt(i + pLen) - 'a' ] == -1 ) { --differ; } else if (count[s.charAt(i + pLen) - 'a' ] == 0 ) { ++differ; } ++count[s.charAt(i + pLen) - 'a' ]; if (differ == 0 ) { ans.add(i + 1 ); } } return ans; } }
[sol2-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class Solution { public IList<int > FindAnagrams (string s, string p ) { int sLen = s.Length, pLen = p.Length; if (sLen < pLen) { return new List<int >(); } IList<int > ans = new List<int >(); int [] count = new int [26 ]; for (int i = 0 ; i < pLen; ++i) { ++count[s[i] - 'a' ]; --count[p[i] - 'a' ]; } int differ = 0 ; for (int j = 0 ; j < 26 ; ++j) { if (count[j] != 0 ) { ++differ; } } if (differ == 0 ) { ans.Add(0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { if (count[s[i] - 'a' ] == 1 ) { --differ; } else if (count[s[i] - 'a' ] == 0 ) { ++differ; } --count[s[i] - 'a' ]; if (count[s[i + pLen] - 'a' ] == -1 ) { --differ; } else if (count[s[i + pLen] - 'a' ] == 0 ) { ++differ; } ++count[s[i + pLen] - 'a' ]; if (differ == 0 ) { ans.Add(i + 1 ); } } return ans; } }
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class Solution {public : vector<int > findAnagrams (string s, string p) { int sLen = s.size (), pLen = p.size (); if (sLen < pLen) { return vector <int >(); } vector<int > ans; vector<int > count (26 ) ; for (int i = 0 ; i < pLen; ++i) { ++count[s[i] - 'a' ]; --count[p[i] - 'a' ]; } int differ = 0 ; for (int j = 0 ; j < 26 ; ++j) { if (count[j] != 0 ) { ++differ; } } if (differ == 0 ) { ans.emplace_back (0 ); } for (int i = 0 ; i < sLen - pLen; ++i) { if (count[s[i] - 'a' ] == 1 ) { --differ; } else if (count[s[i] - 'a' ] == 0 ) { ++differ; } --count[s[i] - 'a' ]; if (count[s[i + pLen] - 'a' ] == -1 ) { --differ; } else if (count[s[i + pLen] - 'a' ] == 0 ) { ++differ; } ++count[s[i + pLen] - 'a' ]; if (differ == 0 ) { ans.emplace_back (i + 1 ); } } return ans; } };
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 func findAnagrams (s, p string ) (ans []int ) { sLen, pLen := len (s), len (p) if sLen < pLen { return } count := [26 ]int {} for i, ch := range p { count[s[i]-'a' ]++ count[ch-'a' ]-- } differ := 0 for _, c := range count { if c != 0 { differ++ } } if differ == 0 { ans = append (ans, 0 ) } for i, ch := range s[:sLen-pLen] { if count[ch-'a' ] == 1 { differ-- } else if count[ch-'a' ] == 0 { differ++ } count[ch-'a' ]-- if count[s[i+pLen]-'a' ] == -1 { differ-- } else if count[s[i+pLen]-'a' ] == 0 { differ++ } count[s[i+pLen]-'a' ]++ if differ == 0 { ans = append (ans, i+1 ) } } return }
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 var findAnagrams = function (s, p ) { const sLen = s.length , pLen = p.length ; if (sLen < pLen) { return []; } const ans = []; const count = Array (26 ).fill (0 ); for (let i = 0 ; i < pLen; ++i) { ++count[s[i].charCodeAt () - 'a' .charCodeAt ()]; --count[p[i].charCodeAt () - 'a' .charCodeAt ()]; } let differ = 0 ; for (let j = 0 ; j < 26 ; ++j) { if (count[j] !== 0 ) { ++differ; } } if (differ === 0 ) { ans.push (0 ); } for (let i = 0 ; i < sLen - pLen; ++i) { if (count[s[i].charCodeAt () - 'a' .charCodeAt ()] === 1 ) { --differ; } else if (count[s[i].charCodeAt () - 'a' .charCodeAt ()] === 0 ) { ++differ; } --count[s[i].charCodeAt () - 'a' .charCodeAt ()]; if (count[s[i + pLen].charCodeAt () - 'a' .charCodeAt ()] === -1 ) { --differ; } else if (count[s[i + pLen].charCodeAt () - 'a' .charCodeAt ()] === 0 ) { ++differ; } ++count[s[i + pLen].charCodeAt () - 'a' .charCodeAt ()]; if (differ === 0 ) { ans.push (i + 1 ); } } return ans; };
复杂度分析
时间复杂度:O(n+m+\Sigma),其中 n 为字符串 s 的长度,m 为字符串 p 的长度,其中\Sigma 为所有可能的字符数。我们需要 O(m) 来统计字符串 p 中每种字母的数量;需要 O(m) 来初始化滑动窗口;需要 O(\Sigma) 来初始化 differ;需要 O(n-m) 来滑动窗口并判断窗口内每种字母的数量是否与字符串 p 中每种字母的数量相同,每次判断需要 O(1) 。因为 s 和 p 仅包含小写字母,所以 \Sigma = 26。
空间复杂度:O(\Sigma)。用于存储滑动窗口和字符串 p 中每种字母数量的差。