LC 438.找到字符串中所有字母异位词

438. 找到字符串中所有字母异位词

给定两个字符串 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 . l e n g t h , p . l e n g t h ≤ 3 ⋅ 1 0 4 1 \leq s.length, p.length \leq 3 \cdot 10^4 1s.length,p.length3104
  • sp 仅包含小写字母

解法一(计数标识)

思路分析:

  1. 首先考虑特殊情况,当s.length < p.length时,s不存在异位词子串,返回null
  2. 然后对字符串p中的字符进行计数,并拼接成一个字符串,即 异位词的 标识字符串。
  3. 再对字符串s,进行遍历,寻找符合条件的子串

实现代码如下:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int lenP = p.length();    // 字符串p的长度
        int lenS = s.length();    // 字符串s的长度
        List<Integer> ans = new ArrayList<>();    // 存储返回结果
        // 存储标识
        String flag = getFlagStr(p, 0, lenP);
        // 遍历字符串s 寻找符合条件的异位词
        for (int i = 0; i+lenP <= lenS; ++i) {   // 遍历
            String flagStr = getFlagStr(s, i, i+lenP);
            if (flagStr.equals(flag)) {
                ans.add(i);
            }
        }
        return ans;
    }
    // 根据字符串中的字符及数量拼接成字符串 并返回
    public String getFlagStr(String str, int start, int end) {
        int[] count = new int[26];
        for (int i = start; i < end; ++i) {
            count[ str.charAt(i) - 'a' ] ++;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 26; ++i) {
            if (count[i] > 0) {
                sb.append(i + 'a').append(count[i]);
            }
        }
        return new String(sb);
    }
}

提交结果如下:

解答成功:
    执行耗时:483 ms,击败了19.50% 的Java用户
    内存消耗:43.3 MB,击败了41.64% 的Java用户

复杂度分析:

  • 时间复杂度: O ( ( n − m + 1 ) ( m + ∣ Σ ∣ ) ) O((n-m+1)(m+|Σ|)) O((nm+1)(m+∣Σ∣)),其中n指字符串s的长度,m指字符串p的长度,不考虑equals()方法的时间复杂度,且 ∣ Σ ∣ = 26 |Σ| = 26 ∣Σ∣=26,因为需要对26个字母进行计数
  • 空间复杂度: O ( n − m + ∣ Σ ∣ ) O(n-m+|Σ|) O(nm+∣Σ∣),其中返回结果最多存储n-m,此外还有计数数组,对于调用函数的开销不计算在内。

解法二(滑动窗口+计数)

思路分析:

  1. 建立两个数组,并分别用来实时统计字符串s和p的字符,然后通过滑动窗口来改变统计字符串s的字符数量,如此减少了对一些字符的重复统计
  2. 确认窗口长度为 p 字符串长度,然后sCount数组则为窗口内字符的统计
  3. 每次将窗口向前移动一步,并动态改变窗口内的字符统计数组sCount
  4. 移动窗口后,与异位词的pCount统计数组进行比较,为异位词则将窗口起始位置保存

实现代码如下:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int lenP = p.length();    // 字符串p的长度
        int lenS = s.length();    // 字符串s的长度

        List<Integer> ans = new ArrayList<>();    // 存储返回结果
        if (lenS < lenP)     // 若字符串s的长度小于字符串p 则直接返回空列表
            return ans;

        int[] sCount = new int[26];        // 统计s中字符的计数数组
        int[] pCount = new int[26];        // 统计p中字符的计数数组
        // 统计p字符串的字符
        for (int i = 0; i < lenP; ++i) {
            ++ pCount[p.charAt(i) - 'a'];
            ++ sCount[s.charAt(i) - 'a'];    // 先统计s中起始索引为0长度为lenP的子串
        }
        if (Arrays.equals(sCount, pCount)) {
            ans.add(0);        // 若子串符合要求 则存储其起始索引
        }
        for (int i = 0; i+lenP < lenS; ++i) {
            -- sCount[s.charAt(i) - 'a'];    // 排除左窗口起始位置的字符
            ++ sCount[s.charAt(i+lenP) - 'a'];    // 新增右窗口处的字符
            if (Arrays.equals(sCount, pCount)) {
                ans.add(i+1);    // 窗口内子串 为异位词 则将起始位置保存
            }
        }
        return ans;
    }
}

提交结果如下:

解答成功:
    执行耗时:7 ms,击败了90.96% 的Java用户
    内存消耗:42.5 MB,击败了91.01% 的Java用户

复杂度分析:

  • 时间复杂度: O ( m + ( n − m ) × ∣ Σ ∣ ) O(m+(n-m)\times∣Σ∣) O(m+(nm)×Σ),m为字符串p的长度,n为字符串s的长度,∣Σ∣为统计数组的长度,每次判断s的字串是否与p为异位词,需要花费 O ( ∣ Σ ∣ ) O(∣Σ∣) O(Σ)
  • 空间复杂度: O ( ∣ Σ ∣ ) O(∣Σ∣) O(Σ),不包括返回结果的存储,使用了两个统计字符的数组来存储字符数目

解法三(滑动窗口优化)

思路分析:

  1. 当两个字符串包含的字符及数目相同时,即符合异位词,因此对于s中的子串与字符串p的字符差异个数,可以使用一个变量distance来标记
  2. 当distance为0时,则说明此时符合异位词,并将窗口的起始索引保存到返回值中。

实现代码如下:

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        int lenP = p.length();    // 字符串p的长度
        int lenS = s.length();    // 字符串s的长度
        List<Integer> ans = new ArrayList<>();    // 存储返回结果
        if (lenS < lenP)     // 若字符串s的长度小于字符串p 则直接返回空列表
            return ans;
        int[] count = new int[26];    // 用于统计字符数
        int distance = 0;    // 用于计数 s子串与字符串p的字符差异个数
        for (int i = 0; i < lenP; ++i) {
            -- count[p.charAt(i) - 'a'];    // p 字符数减少
            ++ count[s.charAt(i) - 'a'];    // s 字符数增加
        }
        for (int number : count) {    // 判断s中[0, lenP)的字串是否为 异位词
            if (number != 0) {
                ++ distance;
            }
        }
        if (distance == 0) {    // 说明s中[0,lenP)的子串为异位词 将其起始索引保存
            ans.add(0);
        }
        for (int i = 0; i+lenP < lenS; ++i) {
            // 排除左边界字符
            int chL = s.charAt(i) - 'a';
            -- count[chL];
            if (count[chL] == -1) {    // 说明子串中该字符数目 由符合变不符合
                ++ distance;    // 即不符合条件的字符数+1
            } else if (count[chL] == 0) {    // 说明子串中该字符数 由不符合变符合
                -- distance;    // 即符合条件的字符数-1
            }
            // 添加右边界字符
            int chR = s.charAt(i+lenP) - 'a';
            ++ count[chR];
            if (count[chR] == 1) {    // 说明子串中该字符数目 由符合变不符合
                ++ distance;    // 即不符合条件的字符数+1
            } else if (count[chR] == 0) {    // 说明子串中该字符数 由不符合变符合
                -- distance;    // 即不符合条件的字符数-1
            }
            // 判断新的子串是否 符合异位词
            if (distance == 0) {
                ans.add(i+1);
            }
        }
        return ans;
    }
}

提交结果如下:

解答成功:
    执行耗时:6 ms,击败了93.93% 的Java用户
    内存消耗:42.7 MB,击败了66.14% 的Java用户

复杂度分析:

  • 时间复杂度: O ( m + n + Σ ) O(m+n+Σ) O(m+n+Σ),n为字符串s的长度,m为字符串p的长度, O ( Σ ) O(Σ) O(Σ)为初始化distance的时间复杂度
  • 空间复杂度: O ( Σ ) O(Σ) O(Σ)。用于存储滑动窗口和字符串 p 中每种字母数量的差。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值