欢迎光临
我们一直在努力

hot 100 第九题 9.找到字符串中所有字母异位词

题目:

给定两个字符串 s 和 p,找到 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" 的异位词。

核心问题

字母异位词要求:每个字符的出现次数完全相同

核心思想就一句话:字母异位词 = 每个字母出现的次数完全相同。

为什么不能只判断"字母存在"?

p = "aab"

错误判断: "aaa" 也是异位词
原因: a 存在 ✓, a 存在 ✓, a 存在 ✓
但实际上 p 里只有 2 个 a,不是 3 个!

正确判断: 必须检查频率
p: a 出现 2 次, b 出现 1 次
"aaa": a 出现 3 次, b 出现 0 次 ✗ 不匹配
"aba": a 出现 2 次, b 出现 1 次 ✓ 匹配!

如何判断频率相同?

用字符计数数组:

p = "abc"
pCount = [1, 1, 1, 0, 0, …, 0]
a b c d e z

对于 s 中的每个长度为 3 的窗口,也统计一个 sCount,然后对比:

s = "cbaebabacd"

窗口 "cba":
sCount = [1, 1, 1, 0, …] ✓ 和 pCount 完全一样!
a b c

窗口 "bae":
sCount = [1, 1, 0, 0, 1, …] ✗ c=0, e=1,不匹配
a b c d e

滑动窗口怎么更新计数?

不需要每次都重新统计整个窗口,只需要:

窗口右移 = 加入右边的新字符 + 移除左边的旧字符

"cba" → "bae"
↑删除 ↑新增

操作:
sCount[c]– (移除 c)
sCount[e]++ (加入 e)

完整过程演示

s = "cbaebabacd", p = "abc"
pCount: [1,1,1,0,0,…,0]
a b c

i=0: 加入c → sCount:[0,0,1,0,0,…]
i=1: 加入b → sCount:[0,1,1,0,0,…]
i=2: 加入a → sCount:[1,1,1,0,0,…] ✓ 匹配!记录索引0

i=3: 加入e, 移除c → sCount:[1,1,0,0,1,…] ✗
i=4: 加入b, 移除b → sCount:[1,1,0,0,1,…] ✗
i=5: 加入a, 移除a → sCount:[1,1,0,0,1,…] ✗

本质

这是滑动窗口 + 固定长度 + 频率统计的组合:

  • 窗口长度固定 = p 的长度
  • 窗口内容 = 用数组统计每个字母出现次数
  • 判断条件 = 两个数组完全相等
  • 题解一

    class Solution {
    public List<Integer> findAnagrams(String s, String p) {
    List<Integer> result = new ArrayList<>();
    if (s.length() < p.length()) return result;

    int[] pCount = new int[26];
    int[] sCount = new int[26];

    for (char c : p.toCharArray()) {
    pCount[c – 'a']++;
    }

    for (int i = 0; i < s.length(); i++) {
    sCount[s.charAt(i) – 'a']++;

    if (i >= p.length()) {
    sCount[s.charAt(i – p.length()) – 'a']–;
    }

    if (Arrays.equals(pCount, sCount)) {
    result.add(i – p.length() + 1);
    }
    }

    return result;
    }
    }

    缺点:这个方法时间复杂度是 O(n×m),会很慢,运行大概率超时。

    题解二

    class Solution {
    public List<Integer> findAnagrams(String s, String p) {
    List<Integer> result = new ArrayList<>();
    if (s.length() < p.length()) return result;

    int[] pCount = new int[26];
    int[] sCount = new int[26];

    for (char c : p.toCharArray()) {
    pCount[c – 'a']++;
    }

    for (int i = 0; i < s.length(); i++) {
    sCount[s.charAt(i) – 'a']++;

    if (i >= p.length()) {
    sCount[s.charAt(i – p.length()) – 'a']–;
    }

    if (Arrays.equals(pCount, sCount)) {
    result.add(i – p.length() + 1);
    }
    }

    return result;
    }
    }

    核心技巧

    这段代码巧妙在只用一次遍历就维护了滑动窗口:

    • 不需要嵌套循环重新统计窗口
    • 每次只做 +1 和 -1 操作,效率极高
    • 时间复杂度 O(n),空间复杂度 O(1)

    if (Arrays.equals(pCount, sCount)) {
    result.add(i – p.length() + 1);
    }
    ```
    **检查当前窗口是否匹配**。

    – `Arrays.equals(pCount, sCount)` 比较两个数组是否完全相等
    – 如果相等,说明当前窗口是字母异位词
    – `i – p.length() + 1` 计算**窗口的起始索引**

    **为什么是 `i – p.length() + 1`?**

    – 窗口右边界是 `i`
    – 窗口长度是 `p.length()`
    – 所以左边界是 `i – p.length() + 1`

    举例:`i = 4`, `p.length() = 3`,窗口是 `[2, 3, 4]`,起始索引是 `4 – 3 + 1 = 2`

    ## 完整演示
    ```
    s = "cbaebabacd", p = "abc" (长度3)
    pCount = [1, 1, 1, 0, 0, …, 0]
    a b c

    i=0: 加入 s[0]='c'
    sCount = [0,0,1,0,0,…]
    窗口长度=1 < 3,不移除
    不匹配

    i=1: 加入 s[1]='b'
    sCount = [0,1,1,0,0,…]
    窗口长度=2 < 3,不移除
    不匹配

    i=2: 加入 s[2]='a'
    sCount = [1,1,1,0,0,…]
    窗口长度=3,不移除 (i=2 < p.length()=3)
    匹配! → 添加 2-3+1=0

    i=3: 加入 s[3]='e'
    sCount = [1,1,1,0,1,…]
    i=3 >= p.length()=3,移除 s[3-3]='c'
    sCount = [1,1,0,0,1,…]
    不匹配

    i=4: 加入 s[4]='b'
    sCount = [1,2,0,0,1,…]
    移除 s[4-3]=s[1]='b'
    sCount = [1,1,0,0,1,…]
    不匹配

    …以此类推

    赞(0)
    未经允许不得转载:171主机测评 » hot 100 第九题 9.找到字符串中所有字母异位词
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址