一分钟解决 | 高频面试题——找到字符串中所有字母异位词

一、题目描述

给定两个字符串 sp,找到 s ****中所有 p ****的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

示例 1:

makefile 复制代码
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

makefile 复制代码
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • sp 仅包含小写字母

二、题解

反面题解(本人的)

js 复制代码
var findAnagrams = function(s, p) {
    const result = [];
    if (s.length < p.length) return result;

    const primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101];
    const charToPrime = new Map();
    for (let i = 0; i < 26; i++) {
        charToPrime.set(String.fromCharCode('a'.charCodeAt(0) + i), primes[i]);
    }

    let target = 1;
    for (const char of p) {
        target *= charToPrime.get(char);
    }

    let left = 0, right = p.length - 1;
    let current = 1;
    for (let i = left; i <= right; i++) {
        current *= charToPrime.get(s[i]);
    }

    while (right < s.length) {
        if (current === target) {
            result.push(left);
        }
        // 这里应该先除然后加完再成,不然不对
        current /= charToPrime.get(s[left]);
        left++;
        right++;
        if (right < s.length) {

            current *= charToPrime.get(s[right]);
        }
    }

    return result;
};

问题

  • 数值溢出: 即使你使用了相对较小的质数,当字符串 p 稍长时,targetcurrent 的值很容易变得非常大,超出 JavaScript Number 类型的安全整数范围 (Number.MAX_SAFE_INTEGER),从而导致精度丢失和比较错误。

正解(滑动窗口+出现频次)

js 复制代码
var findAnagrams = function(s, p) {
    const result = [];
    if (s.length < p.length) return result;

    const pLen = p.length;
    const sLen = s.length;

    // 初始化频率数组(26个小写字母)
    const pCount = new Array(26).fill(0);
    const sCount = new Array(26).fill(0);

    // 统计 p 的字符频率
    for (let i = 0; i < pLen; i++) {
        pCount[p.charCodeAt(i) - 'a'.charCodeAt(0)]++;
    }

    // 初始化滑动窗口(窗口大小为 pLen)
    for (let i = 0; i < pLen; i++) {
        sCount[s.charCodeAt(i) - 'a'.charCodeAt(0)]++;
    }

    // 比较初始窗口是否匹配
    if (arraysEqual(pCount, sCount)) {
        result.push(0);
    }

    // 滑动窗口:每次右移一位,更新频率数组
    for (let i = pLen; i < sLen; i++) {
        // 移除左边界的字符
        sCount[s.charCodeAt(i - pLen) - 'a'.charCodeAt(0)]--;
        // 添加右边界的字符
        sCount[s.charCodeAt(i) - 'a'.charCodeAt(0)]++;

        // 检查当前窗口是否匹配
        if (arraysEqual(pCount, sCount)) {
            result.push(i - pLen + 1);
        }
    }

    return result;
};

// 辅助函数:比较两个频率数组是否相等
function arraysEqual(a, b) {
    for (let i = 0; i < a.length; i++) {
        if (a[i] !== b[i]) return false;
    }
    return true;
}

核心思想

  1. 频率统计:

    • 它使用两个数组 pCountsCount 来存储字符串 p 和滑动窗口中字符的频率。 pCount[i] 表示字符 'a' + i 在字符串 p 中出现的次数。 sCount 存储相同的信息,但针对的是在字符串 s 中的滑动窗口。
  2. 滑动窗口:

    • 它维护一个长度等于 p 的窗口,在字符串 s 上滑动。
    • 窗口的初始位置是 s 的前 pLen 个字符。
    • 每次滑动,窗口向右移动一个字符。
    • 在移动后,函数更新 sCount 以反映窗口中字符频率的变化。 具体来说,它从 sCount 中移除窗口左边界的字符,并添加窗口右边界的字符。
  3. 判断异位词:

    • 在每次窗口滑动后,函数比较 pCountsCount。 如果这两个数组相等,这意味着窗口中的字符是 p 的一个异位词,因此将窗口的起始位置添加到 result 数组中。 arraysEqual 函数用于比较两个频率数组。

详细解释

  1. var findAnagrams = function(s, p) {:定义函数,接收两个字符串s和p作为输入。

  2. const result = [];:初始化结果数组,用于存放异位词起始下标。

  3. if (s.length < p.length) return result;:如果s比p短,直接返回空数组(不可能有异位词)。

  4. const pCount = new Array(26).fill(0);:创建数组pCount,统计p中每个字母出现的次数。

  5. const sCount = new Array(26).fill(0);:创建数组sCount,统计滑动窗口中每个字母出现的次数。

  6. for (let i = 0; i < p.length; i++) { ... }: 循环遍历字符串p

    * pCount[p.charCodeAt(i) - 'a'.charCodeAt(0)]++;: 统计p中每个字符出现的次数,并存储在pCount数组中。

    * sCount[s.charCodeAt(i) - 'a'.charCodeAt(0)]++;: 初始化滑动窗口,统计第一个窗口(长度和p相同)中每个字符出现的次数,存储在sCount中。

  7. for (let i = 0; i <= s.length - p.length; i++) { ... }: 滑动窗口循环遍历字符串ss.length - p.length计算了需要滑动的最大次数, 因为超过这个次数后, 剩余字符串的长度将小于 p的长度, 无法形成异位词.

  8. if (areArraysEqual(sCount, pCount)) { result.push(i); }: 比较sCountpCount, 如果二者相同则表示当前滑动窗口内的子字符串是p的一个异位词, 将窗口起始位置i添加到result

  9. if (i < s.length - p.length) { ... }: 在更新滑动窗口字符统计前,检查窗口是否可以滑动(避免数组越界)

    • sCount[s.charCodeAt(i) - 'a'.charCodeAt(0)]--;: 移除窗口起始位置的字符统计
    • sCount[s.charCodeAt(i + p.length) - 'a'.charCodeAt(0)]++;: 添加新进入窗口的字符统计
  10. return result;:返回结果列表。

  11. function areArraysEqual(arr1, arr2) { ... }: 辅助函数,用于比较两个数组是否相等. 遍历数组,如果对应索引的值不相等,说明数组不相等.

三、结语

再见!

相关推荐
程序员爱钓鱼2 小时前
Cargo 包管理器详解:Rust 开发必会工具
后端·面试·rust
大鱼>2 小时前
AI+货物追踪:全链路货物可视化追踪系统
人工智能·深度学习·算法·机器学习
大江东去浪淘尽千古风流人物2 小时前
【SLAM】slam高动态位姿估计全链路拆解
算法·面试·职场和发展·视觉里程计·slam·vio·大疆
想你依然心痛2 小时前
量子计算对嵌入式密码学的挑战与后量子算法准备
算法·密码学·量子计算
名字还没想好☜4 小时前
Next.js App Router 数据获取:fetch 缓存、revalidate 与 dynamic 一次讲清
前端
Jerry6 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry8 小时前
LeetCode 459. 重复的子字符串
算法
成都渲染101云渲染66668 小时前
如何在3ds Max中实现更快、更高质量的渲染
前端·javascript·人工智能
闲猫10 小时前
Spring AI 对接Deepseek ChatModel 聊天对话
java·前端·spring
海石10 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode