Leetcode 438:找到字符串中所有字母异位词(最详细解决方案!!!)

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 sp 的长度都不超过 20100。

说明:

  • 字母异位词指字母相同,但排列不同的字符串。
  • 不考虑答案输出的顺序。

示例 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" 的字母异位词。

解题思路

首先想到的解法,就是将p的所有组合存到一个数组p_per中,接着扫描s,判断s[i:i + p_len]是否在这个数组中。

class Solution:
    def findAnagrams(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: List[int]
        """
        import itertools
        result = []
        p_len, s_len = len(p), len(s)
        if s_len < p_len:
            return result

        p_per = []
        for i in itertools.permutations(p): #通过itertools中的permutations产生所有组合
            p_per.append(''.join(i))

        for i in range(s_len):
            if i + p_len <= s_len and s[i:i + p_len] in p_per:
                result.append(i)
        return result

但是这种解法的空间复杂度很高O(per(p)*len(p))。而且如果输入sp都非常长的情况会出现超时,因为p的组合数太多了。

那么我们有没有即快速又节省空间的算法呢?

其实我们通过分析发现,所谓的字母异位词,只要保证p中的各个字母个数s的字串中相同即可。

class Solution:
    def findAnagrams(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: List[int]
        """
        from collections import Counter
        s_len, p_len = len(s), len(p)
        pChar, sChar = Counter(p), Counter()

        result = []
        for i in range(s_len):
            sChar[s[i]] += 1
            if i >= p_len:
                sChar[s[i - p_len]] -= 1
                if sChar[s[i - p_len]] == 0:
                    del sChar[s[i - p_len]]

            if pChar == sChar:
                result.append(i - p_len + 1)
        return result

我们通过分析发现上面算法的时间复杂度是o(n^2)级别的(pChar == sChar要花费O(n)的时间),我们有没有O(n)级别的算法呢?

参照这篇Leetcode 3:无重复字符的最长子串文章最后提到的思路,我们可以很快速的解决这个问题。

我们通过一个变量count记录p元素的个数

c  b  a  e  b  a  b  a  c  d
l     r

窗口在移动的过程中,如果窗口中的元素在p中的话,那么我们记录变量count--(表示一个在pChar中的元素移入窗口)。

c  b  a  e  b  a  b  a  c  d
l        r

如果移动的过程中窗口右边超过了p_len,我们就要判断窗口左边的元素是不是在p中,如果在的话,那么count++(表示一个在pChar中的元素移除窗口)。

c  b  a  e  b  a  b  a  c  d
   l     r

如果count变成了0,表示窗口中的元素,pChar中都有。

class Solution:
    def findAnagrams(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: List[int]
        """
        from collections import Counter
        s_len, p_len = len(s), len(p)
        count = p_len
        pChar = Counter(p)

        result = []
        for i in range(s_len):
            if pChar[s[i]] >= 1:
                count -= 1
            pChar[s[i]] -= 1
            if i >= p_len:
                if pChar[s[i - p_len]] >= 0:
                    count += 1
                pChar[s[i - p_len]] += 1
            if count == 0:
                result.append(i - p_len + 1)

        return result

对于这个算法有几点要注意,首先count表示p中的元素个数,如果s中的非p内元素,我们不用对count操作。另外,不论s中的元素是否在p中,我们都要对pChar操作(我们在pChar初始化的时候给与p中元素计数,这样就区分开来了p中元素和非p中元素)。

现在这个算法的时间复杂度就是O(n)级别的了。

该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

你可能感兴趣的:(Problems,leetcode解题指南)