🧠 小哆啦解题记——谁偷调了我的字母顺序?

96 阅读3分钟

📅 2025-07-08|Day 34
题号:242. 有效的字母异位词 - 力扣(LeetCode)


第一章:静香的诗,胖虎也能写?

“你看!”静香一边甩着 ponytail,一边递过来两张纸:

s = "anagram"
t = "nagaram"

“你敢信?胖虎说他是‘原创’诗人,他写的 t 明明是把我 s 字母顺序调了个顺序!”

大雄挠头:“那也不能直接说是抄袭吧?字母顺序不一样,怎么说是‘一样’的呢?”

哆啦A梦一口咬掉半个铜锣烧:“这就叫——字母异位词(Anagram) 。”


第二章:异位词到底是什么鬼?

“字母异位词”指的是:
✅ 两个字符串长度相等
✅ 每个字符出现的次数完全相同
✅ 顺序可以不同(谁先谁后无所谓)

哆啦A梦递给大雄一块“排序魔方”:“你可以先试试排序法,简单粗暴。”


第三章:排序法 · 快速检测器上线!

function isAnagram(s: string, t: string): boolean {
    if (s.length !== t.length ) return false;

    let sArr: string[] = s.split('').sort();
    let tArr: string[] = t.split('').sort();

    return sArr.join('') === tArr.join('');
}

🧠 思路:排序后相等,就说明字母组成一模一样。
⏱ 时间复杂度:O(n log n);优点是写法清晰,缺点是排序浪费时间。

静香:
“不错,但如果我写了一首十万字的长诗,你还用 .sort(),电脑得原地升天。”


第四章:双 Map 频次追踪术 · 我自己数!

哆啦A梦:
“那就靠你自己数数每个字母出现了几次!”

于是大雄写出双 Map 频率比较器(正是你写的那一版):

function isAnagram(s: string, t: string): boolean {
    if (s.length !== t.length) return false;

    let sArr: string[] = s.split("");
    let tArr: string[] = t.split("");
    let sMap: Map<string, number> = new Map();
    let tMap: Map<string, number> = new Map();

    for (let i = 0; i < s.length; i++) {
        // 更新 sMap
        if (sMap.has(sArr[i])) {
            let num = sMap.get(sArr[i])!;
            sMap.set(sArr[i], num + 1);
        } else {
            sMap.set(sArr[i], 1);
        }

        // 更新 tMap
        if (tMap.has(tArr[i])) {
            let num = tMap.get(tArr[i])!;
            tMap.set(tArr[i], num + 1);
        } else {
            tMap.set(tArr[i], 1);
        }
    }

    // 合并 key,防止某个 map 缺 key
    let aSet: string[] = Array.from(new Set([...sMap.keys(), ...tMap.keys()]));

    for (let i = 0; i < aSet.length; i++) {
        if (tMap.get(aSet[i]) !== sMap.get(aSet[i])) return false;
    }

    return true;
}

哆啦A梦点头:“你这次写得很好!不过可以再简洁点。”


第五章:简化!优化!干净利落!

大雄瞪大眼:“能更短?”

哆啦A梦:“当然!你完全可以把两个 Map 合成一个!”

✅ 优化版:一个 Map 解决战斗(频次差值法)

function isAnagram(s: string, t: string): boolean {
    if (s.length !== t.length) return false;

    let map: Map<string, number> = new Map();

    for (let i = 0; i < s.length; i++) {
        map.set(s[i], (map.get(s[i]) || 0) + 1);
        map.set(t[i], (map.get(t[i]) || 0) - 1);
    }

    for (let val of map.values()) {
        if (val !== 0) return false;
    }

    return true;
}

🧠 思路:一个 Map 同时统计 s 的字符 +1,t 的字符 -1,如果最后每个字母都为 0,说明字符频次相等!


第六章:终极版本(全 ASCII 字符)提升性能!

如果你知道字符串只包含小写字母(a-z),还可以用数组代替 Map,加速:

function isAnagram(s: string, t: string): boolean {
    if (s.length !== t.length) return false;

    const freq: number[] = new Array(26).fill(0);
    const aCode = 'a'.charCodeAt(0);

    for (let i = 0; i < s.length; i++) {
        freq[s.charCodeAt(i) - aCode]++;
        freq[t.charCodeAt(i) - aCode]--;
    }

    return freq.every(count => count === 0);
}

🧪 测试一下吧!

isAnagram("anagram", "nagaram"); //true
isAnagram("rat", "car");         //false
isAnagram("aacc", "ccac");       //false
isAnagram("", "");               //true

📌 技术启示录:

解法优点缺点复杂度
排序法简洁易写时间略慢 O(n log n)时间+空间
双 Map通用、适合全字符集写法稍冗O(n)
差值 Map 优化简洁高效,双向处理要理解减法逻辑O(n)
频次数组法最快,空间固定 O(1)仅适用于小写字母O(n)

🌌 终章:字符重组的算法之美

大雄仰望窗外星空:

“原来一个词和另一个词之间的关系,
并不在乎顺序,
而是——他们有没有一样的‘灵魂’。”

哆啦A梦笑着补充:

“而算法,正是帮我们找出那些‘灵魂是否一致’的工具。”