LeetCode 438-找到字符串中所有字母异位词

78 阅读5分钟

题目

给定两个字符串 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 仅包含小写字母

思路

解法一: 滑动窗口+2个频次表+distance控制(自)

本解答参考76题的思路,只需要做以下1个改动:

  • distance==pLen时,停止滑动时,判断right-left==pLen,保证区间内的值恰好等于pLen的所有字符, 如果不满足,left指针继续往右移动;如果满足就计入答案。

代码一

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        sLen = len(s)
        pLen = len(p)
        if sLen == 0 or pLen == 0 or sLen < pLen:
            return []
        
        # 频次表
        pFreq = [0 for _ in range(128)]
        winFreq = [0 for _ in range(128)]
        for c in p:
            pFreq[ord(c)] += 1

        # 滑动窗口模板
        res = []
        left = 0
        right = 0
        distance = 0
        while right < sLen:
            if winFreq[ord(s[right])] < pFreq[ord(s[right])]:
                distance += 1
            winFreq[ord(s[right])] += 1
            right += 1

            # 当频次相同,判断窗口长度与pLen是否相等,是的话就保存答案
            while distance == pLen:
                if right - left == pLen:
                    res.append(left)
                
                if winFreq[ord(s[left])] == pFreq[ord(s[left])]:
                    distance -= 1
                
                winFreq[ord(s[left])] -= 1
                left += 1
        return res

解法二: 定长的滑动窗口+2个频次表

对于解法一,虽然使用的是滑动窗口,但是题目有个条件没充分利用,那就是满足题意的窗口长度只可能等于pLen,解答一只是在筛选答案的时候利用上了,导致可能会出现很多次判断,其实可以让滑动窗口一开始就固定长度为pLen,这也是官方解答的第一个,值得借鉴。

具体方案:首先使用两个变量 sFreqpFreq 计算s和p的各个字符出现次数,通过判断各个pLen窗口下 sFreq==pFreq,来判断滑动窗口是否满足条件

  • 先遍历p和s的pLen长度窗口,统计sFreq和pFreq的字符出现次数,如果相等,那么说明第一个滑动窗口满足题意,也就是left从0开始的满足题意,0计入答案
  • 右指针right从pLen开始,左指针left从0开始,每次left、right移动一个位置,然后right位置的s[right]计入sFreq中,同时将s[left]从sFreq中移除,再都判断 sFreq和pFreq是否相等,如果相等,将 left+1 计入答案

代码二

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        # 官解一
        sLen = len(s)
        pLen = len(p)
        if sLen == 0 or pLen == 0 or sLen < pLen:
            return []
        
        res = []
        # 频次表
        pFreq = [0 for _ in range(26)]
        sFreq = [0 for _ in range(26)]
        for i in range(pLen):
            pFreq[ord(p[i]) - 97] += 1
            sFreq[ord(s[i]) - 97] += 1
        
        if pFreq == sFreq:
            res.append(0)

        right = pLen
        left = 0
        while right < sLen:
            sFreq[ord(s[left]) - 97] -= 1
            sFreq[ord(s[right]) - 97] += 1

            if sFreq == pFreq:
                res.append(left+1)
            right += 1
            left += 1
        return res

解法三: 定长的滑动窗口+1个频次表+differ变量判断是否相等

官解2:对于解法二的2个频次表,其实可以更加优化,使用一个频次表 freq 来简化空间复杂度。具体步骤如下:

  • 先遍历s和p的pLen长度,s位置的字符频数+1, p位置的字符频数的-1,然后查看 freq 各个位置上>0的值有多少,记为differ(但是不能计算sum,因为可能存在某个元素多了一个,但是某个元素少了一个,导致sum=0),如果differ==0,说明0位置也满足条件,计入答案中
  • right指针从pLen开始,left指针从0开始,计入right指针,移除left指针,每次同时移动一个位置,保证滑动窗口的长度,判断differ:
    • 移除left指针时,因为是移除,减掉该字符,考虑differ何时应该变化,移除前,如果freq[s[left]] 的元素刚好=1,那么这次移除后,该字符刚好满足,differ-=1;如果该字符之前就是0满足条件,现在要移除的话,字符又不满足条件了,differ+1
    • 加入right指针时,因为是加入,加上该字符,考虑differ何时应该变化,加入前,如果freq[s[right]]的元素刚好=-1,那么这次加入后,该字符刚好满足,differ-=1;如果该字符之前就是0满足条件,现在要加入的话,字符又不满足条件了,differ+1
  • 如果窗口往右移动完成后,differ=0,那么说明left的移除和right的加入,又是一个满足条件的,那么left+1应该计入答案。

代码三: 统计freq的元素>0的differ

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        sLen = len(s)
        pLen = len(p)
        if sLen == 0 or pLen == 0 or sLen < pLen:
            return []
        
        res = []
        # 频次表
        freq = [0 for _ in range(26)]
        for i in range(pLen):
            freq[ord(p[i]) - 97] -= 1
            freq[ord(s[i]) - 97] += 1
        
        differ = [c!=0 for c in freq].count(True)
        if differ == 0:
            res.append(0)

        left = 0
        right = pLen
        while right < sLen:
            if freq[ord(s[left])-97] == 1:
                differ -= 1
            if freq[ord(s[left]) - 97] == 0:
                differ += 1
            freq[ord(s[left]) - 97] -= 1
            
            if freq[ord(s[right])-97] == -1:
                differ -= 1
            if freq[ord(s[right]) - 97] == 0:
                differ += 1
            freq[ord(s[right]) - 97] += 1

            if differ == 0:
                res.append(left+1)
            left += 1
            right += 1
        return res