LeetCode探索(100):438-找到字符串中所有字母异位词

131 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

题目

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

思考

本题难度中等。

首先是读懂题意。给定两个字符串 s 和 p ,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。需要注意的是,异位词是指相同字符组成的不同字符串。

我们可以使用滑动窗口的方法来解题。首先是理解异位词是什么意思。字符串 abc 和 acb 、 bac 、 bca 、 cab 、 cba 等互为异位词;字符串 aab 和 aba 也互为异位词。那么,判断是否为异位词时,我们需要考虑两个字符串的字符种类和出现次数。

判断是否为异位词,比较方便的方法是借助数组。考虑到题目中给出的字符串只包含小写字母,那么我们可以定义字符a对应数组的第一个元素,b对应第二个元素,依此类推。这里的数组中的元素代表字符出现的次数。

我们对字符串s和p中的字符进行计数。假设字符串p的长度为pLen,那么,每隔pLen个字符,我们判断滑动窗口中的字符串是否与字符串p的字符种类及出现次数均相等,若是即互为异位词。

解答

方法一:滑动窗口

/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s, p) {
  const sLen = s.length, pLen = p.length
  if (sLen < pLen) {
    return []
  }
  const ans = []
  // 一共26个小写字母
  const sCount = new Array(26).fill(0)
  const pCount = new Array(26).fill(0)
  // 判断字符串s中的前pLen个字符是否满足
  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)
  }
  // console.log(sCount, pCount)
  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
}
console.log(findAnagrams("cbaebabacd", "abc")) // [0, 6]

复杂度分析:

  • 时间复杂度:O(m+(n−m)×Σ),其中 n 为字符串 s 的长度,m 为字符串 p 的长度,Σ 为所有可能的字符数,Σ=26。
  • 空间复杂度:O(Σ)。用于存储字符串 p 和滑动窗口中每种字母的数量。

参考