0438. 找到字符串中所有字母异位词

题目地址(438. 找到字符串中所有字母异位词)

https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/

题目描述

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

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 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" 的字母异位词。

前置知识

  • Sliding Window

  • 哈希表

思路

咳咳,暴力题解俺就不写了哈,因为和昨天基本一致

来分析一下,首先题中说找到 s 中所有是 p 的字母异位词的字串,就这句话,就包含了如下两个重要信息:

  • 找到符合要求的子串长度都是 p

  • 何为字母异位词?也就是我们不关心 p 这个串的顺序,只关心字母是否出现以及出现的次数,这种问题解决方案一般有两种,一种是利用排序强制顺序,另一种就是用哈希表的方法。

这么一抽象,是不是和昨天那个题很相似呢?那么问题的关键就是:

  • 如何构建滑窗

  • 如何更新状态,也即如何存储 p 串及更新窗口信息

针对问题 1 很容易,因为是长度固定为 p 的滑动窗口,而针对如何存储 p 串这个问题,我们可以考虑用桶来装,这个桶既可以用 26 个元素的数组(作用其实也是哈希表)也可以用哈希表

那么我们解决方案就很明朗了:

  • 初始化个滑窗

  • 不断移动该固定窗口,并用一个 rest 变量来记录剩余待匹配字符的个数

  • 只要当前窗口符合要求,即把窗口左指针下标添加到结果集合中去。

代码

代码支持:Java,Python3

Java Code:

public List<Integer> findAnagrams(String s, String p) {

    List<Integer> res = new LinkedList<>();
    if (s == null || p == null || s.length() < p.length())
        return res;

    int[] ch = new int[26];
    //统计p串字符个数
    for (char c : p.toCharArray())
        ch[c - 'a']++;
    //把窗口扩成p串的长度
    int start = 0, end = 0, rest = p.length();
    for (; end < p.length(); end++) {
        char temp = s.charAt(end);
        ch[temp - 'a']--;
        if (ch[temp - 'a'] >= 0)
            rest--;
    }

    if (rest == 0)
        res.add(0);
    //开始一步一步向右移动窗口。
    while (end < s.length()) {
        //左边的拿出来一个并更新状态
        char temp = s.charAt(start);
        if (ch[temp - 'a'] >= 0)
            rest++;
        ch[temp - 'a']++;
        start++;
        //右边的拿进来一个并更新状态
        temp = s.charAt(end);
        ch[temp - 'a']--;
        if (ch[temp - 'a'] >= 0)
            rest--;
        end++;
        // 状态合法就存到结果集合
        if (rest == 0)
            res.add(start);
    }

    return res;
}

Python 解法具体做法稍有一点不同,没有使用 rest 变量,而是直接取的哈希表的长度。其中 哈希表的 key 是字符,value 是窗口内字符出现次数。这样当 value 为 0 时,我们移除 key,这样当哈希表容量为 0,说明我们找到了一个异位词。

Python3 Code:

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        target = collections.Counter(p)
        ans = []
        for i in range(len(s)):
            if i >= len(p):
                target[s[i - len(p)]] += 1
                if target[s[i - len(p)]] == 0:
                    del target[s[i - len(p)]]
            target[s[i]] -= 1
            if target[s[i]] == 0:
                del target[s[i]]
            if len(target) == 0:
                ans.append(i - len(p) + 1)
        return ans

你也可以将窗口封装成一个类进行操作。虽然代码会更长,但是如果你将窗口类看成黑盒,那么逻辑会很简单。

这里我提供一个 Python3 版本的封装类解法

class FrequencyDict:
   def __init__(self, s):
       self.d = collections.Counter()
       for char in s:
           self.increment(char)

   def _del_if_zero(self, char):
       if self.d[char] == 0:
           del self.d[char]

   def is_empty(self):
       return not self.d

   def decrement(self, char):
       self.d[char] -= 1
       self._del_if_zero(char)

   def increment(self, char):
       self.d[char] += 1
       self._del_if_zero(char)


class Solution:
   def findAnagrams(self, s: str, p: str) -> List[int]:
       ans = []

       freq = FrequencyDict(p)

       for char in s[:len(p)]:
           freq.decrement(char)

       if freq.is_empty():
           ans.append(0)

       for i in range(len(p), len(s)):
           start, end = s[i - len(p)], s[i]
           freq.increment(start)
           freq.decrement(end)
           if freq.is_empty():
               ans.append(i - len(p) + 1)

       return ans

复杂度分析

令 s 的长度为 n。

  • 时间复杂度:$O(n)$

  • 空间复杂度:虽然我们使用了数组(或者哈希表)存储计数信息,但是大小不会超过 26,因此空间复杂度为 $O(1)$。

最后更新于