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

一、题目描述

给定两个字符串 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) { ... }: 辅助函数,用于比较两个数组是否相等. 遍历数组,如果对应索引的值不相等,说明数组不相等.

三、结语

再见!

相关推荐
Kx…………1 分钟前
Day3:个人中心页面布局前端项目uniapp壁纸实战
前端·学习·uni-app·实战·项目
艾醒4 分钟前
探索大语言模型(LLM):Transformer 与 BERT从原理到实践
算法
肠胃炎4 分钟前
认识Vue
前端·javascript·vue.js
七月丶7 分钟前
🛠 用 Node.js 和 commander 快速搭建一个 CLI 工具骨架(gix 实战)
前端·后端·github
艾醒9 分钟前
探索大语言模型(LLM):循环神经网络的深度解析与实战(RNN、LSTM 与 GRU)
算法
砖吐筷筷9 分钟前
我理想的房间是什么样的丨去明日方舟 Only 玩 - 筷筷月报#18
前端·github
七月丶9 分钟前
🔀 打造更智能的 Git 提交合并命令:gix merge 实战
前端·后端·github
iguang11 分钟前
通过实现一个mcp-server来理解mcp
前端
Lafar11 分钟前
OC-runtime使用场景
前端
艾醒11 分钟前
探索大语言模型(LLM):马尔可夫链——从诗歌分析到人工智能的数学工具
深度学习·算法