leetcode
leetcode 401 ~ 450
找到字符串中所有字母异位词

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

难度:

标签:

题目描述

给定两个字符串 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" 的异位词。

 

提示:

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

代码结果

运行时间: 61 ms, 内存: 16.5 MB


/*
 * 思路:
 * 使用滑动窗口算法和Java Stream来解决这个问题。我们首先计算字符串 p 的字符频率,
 * 然后我们在字符串 s 上滑动窗口,每次移动一个字符,并更新窗口内字符的频率。
 * 使用Java Stream的API来简化频率表的比较和更新操作。
 */
import java.util.*;
import java.util.stream.Collectors;
 
public class SolutionStream {
    public List<Integer> findAnagrams(String s, String p) {
        if (s.length() < p.length()) return Collections.emptyList();
 
        Map<Character, Long> pCount = p.chars().mapToObj(c -> (char)c)
                .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
 
        Map<Character, Long> sCount = s.substring(0, p.length()).chars().mapToObj(c -> (char)c)
                .collect(Collectors.groupingBy(c -> c, Collectors.counting()));
 
        List<Integer> result = new ArrayList<>();
        if (sCount.equals(pCount)) {
            result.add(0);
        }
 
        for (int i = p.length(); i < s.length(); i++) {
            char newChar = s.charAt(i);
            char oldChar = s.charAt(i - p.length());
 
            sCount.put(newChar, sCount.getOrDefault(newChar, 0L) + 1);
            sCount.put(oldChar, sCount.get(oldChar) - 1);
 
            if (sCount.get(oldChar) == 0) {
                sCount.remove(oldChar);
            }
 
            if (sCount.equals(pCount)) {
                result.add(i - p.length() + 1);
            }
        }
 
        return result;
    }
}

解释

方法:

该题解采用了滑动窗口的方法。首先,使用一个哈希表 need 记录字符串 p 中每个字符的出现次数。然后,使用另一个哈希表 window 来记录当前窗口中各字符的出现次数。接着,不断扩展右边界 right,直到窗口中包含了 p 中所有字符。此时,开始收缩左边界 left,直到窗口中不再包含 p 的所有字符。在收缩的过程中,如果窗口的长度恰好等于 p 的长度,则说明找到了一个异位词的起始索引,将其添加到结果列表中。重复这个过程,直到遍历完字符串 s。

时间复杂度:

O(n)

空间复杂度:

O(p)

代码细节讲解

🦆
为什么选择使用滑动窗口技术来解决这个问题,而不是其他算法例如排序比较或者使用Trie树?
滑动窗口技术在这个问题中非常高效,因为它可以在O(n)的时间复杂度内完成任务,其中n是字符串s的长度。使用滑动窗口,可以动态地调整窗口的大小并实时检查窗口内的字符串是否为异位词,而不需要对每个子串进行重新排序或重新建立数据结构。相比之下,排序比较需要将每个子串排序后比较,时间复杂度至少为O(n*m*log(m)),其中m是字符串p的长度;使用Trie树则需要构建和维护一个复杂的数据结构,对于简单的异位词检查而言过于复杂且效率不高。因此,滑动窗口因其简洁和高效而成为首选方法。
🦆
在哈希表`need`中,如果某个字符在字符串`p`中出现多次,你是如何处理这种情况的?
在哈希表`need`中,如果字符串`p`中的某个字符出现多次,我们将该字符作为键,其出现次数作为值存入哈希表。这是通过遍历字符串`p`,对每个字符进行计数并更新哈希表实现的。例如,如果`p`中字符`a`出现了两次,则在`need`中存储为`{'a': 2}`。这样,我们就可以确保在检查字符串`s`的子串时,能够准确匹配`p`中每个字符的确切出现次数。
🦆
在滑动窗口中,当`valid`等于`need`的长度时,为什么可以认为窗口内的字符串是一个有效的异位词?
在滑动窗口中,`valid`变量用来计数窗口内满足`need`字符数量要求的不同字符的个数。当`valid`的值等于`need`的长度时,意味着窗口中的每个字符都恰好与`p`中的字符数量相匹配,且所有`p`中的字符都被考虑到了。因此,这确保了窗口中的字符串不仅包含了`p`中的所有字符,而且每个字符的数量也与`p`完全相同,从而构成一个有效的异位词。
🦆
你的算法在处理非常大的字符串`s`时的效率如何?是否有可能出现性能瓶颈?
该算法在处理非常大的字符串`s`时通常效率很高,时间复杂度为O(n),其中n是字符串`s`的长度。算法效率的关键在于滑动窗口的操作,它避免了重复的工作,只需遍历一次字符串`s`即可。然而,如果字符串`s`极其大,或者字符集很广(例如Unicode字符),则哈希表的操作可能会成为性能瓶颈,因为维护和更新哈希表的成本会随着字符集大小增加而增加。此外,如果`p`字符串长度远小于`s`,而`s`包含大量不在`p`中的字符,那么滑动窗口中的很多操作可能是处理这些无关字符,这可能会导致一些效率损失。

相关问题

有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 st 中每个字符出现的次数都相同,则称 st 互为字母异位词。

 

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

 

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母

 

进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

字符串的排列

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false

换句话说,s1 的排列之一是 s2子串

 

示例 1:

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
解释:s2 包含 s1 的排列之一 ("ba").

示例 2:

输入:s1= "ab" s2 = "eidboaoo"
输出:false

 

提示:

  • 1 <= s1.length, s2.length <= 104
  • s1s2 仅包含小写字母