leetcode
leetcode 501 ~ 550
字符串的排列

字符串的排列

难度:

标签:

题目描述

代码结果

运行时间: 66 ms, 内存: 16.1 MB


/*
 * 思路:
 * 使用Java Stream API简化代码,通过流的方式处理字符频率和滑动窗口。
 * 1. 使用Stream构建s1的字符频率数组。
 * 2. 使用IntStream创建滑动窗口并更新窗口字符频率。
 * 3. 每次窗口移动后,比较两个频率数组是否相等。
 */
import java.util.Arrays;
import java.util.stream.IntStream;
 
public class Solution {
    public boolean checkInclusion(String s1, String s2) {
        if (s1.length() > s2.length()) return false;
        int[] freq1 = s1.chars().map(c -> c - 'a').collect(() -> new int[26], (arr, c) -> arr[c]++, (arr1, arr2) -> {
            for (int i = 0; i < arr1.length; i++) arr1[i] += arr2[i];
        });
        int[] freq2 = new int[26];
        IntStream.range(0, s1.length()).forEach(i -> freq2[s2.charAt(i) - 'a']++);
        return IntStream.range(0, s2.length() - s1.length() + 1)
                .anyMatch(i -> {
                    if (i > 0) {
                        freq2[s2.charAt(i - 1) - 'a']--;
                        freq2[s2.charAt(i + s1.length() - 1) - 'a']++;
                    }
                    return Arrays.equals(freq1, freq2);
                });
    }
}

解释

方法:

这个题解使用了滑动窗口的思路。首先用 Counter 统计 s1 中每个字符出现的次数,作为需要满足的条件。然后在 s2 上滑动窗口,扩张和收缩窗口,并统计窗口内每个字符的出现次数。当窗口内某个字符的出现次数与 s1 中对应的次数相等时,说明找到了一个符合条件的字符。当所有字符都符合条件,且窗口大小等于 s1 的长度时,说明找到了一个 s1 的排列,返回 True。如果滑动到 s2 的末尾还没找到,则返回 False。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么在判断窗口内字符数量后,需要比较窗口的大小和s1的长度是否一致?
在检查窗口内字符数量满足s1中字符数量要求后,比较窗口大小和s1的长度可以确认窗口中不包含额外的字符,确保窗口恰好是s1的一个排列。如果窗口大小大于s1长度,则窗口中必然包含不属于s1排列的额外字符,这不符合题目要求。
🦆
在代码中,如果s2中的字符不在s1中,为什么需要重置窗口而不是简单地移动左指针?
当遇到s2中不在s1中的字符时,重置窗口是为了清除所有与s1不匹配的字符的影响,因为这些字符的存在无法形成s1的排列。简单移动左指针可能仍然包含那些不相关的字符,而重置窗口能确保重新开始寻找可能的s1排列,提高效率并简化逻辑。
🦆
如何确保窗口内的字符顺序不会影响判断结果,即只需关注字符的数量而非其排列顺序?
本算法通过使用字符计数器来统计窗口内和s1中的字符数,只要这些字符的计数相匹配,就可以认为窗口内的字符是s1的一个排列。这种方法只关注字符数量的匹配,并不关心字符的实际顺序,因此可以有效地判断是否存在符合条件的排列而无需考虑字符顺序。
🦆
在滑动窗口算法中,valid变量的作用是什么,它如何帮助我们判断当前窗口是否可能包含s1的一个排列?
在滑动窗口算法中,valid变量用于记录窗口内满足s1中字符数量要求的字符种类数。当valid值等于s1中不同字符的种类数时,表示窗口内所有必需的字符都已达到所需数量,此时如果窗口大小恰好等于s1长度,则可以确定这个窗口是s1的一个排列。valid变量是判断窗口状态是否完全满足题目要求的关键因素。

相关问题

最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

 

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

 

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

 

提示:

  • m == s.length
  • n == t.length
  • 1 <= m, n <= 105
  • st 由英文字母组成

 

进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

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

给定两个字符串 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 仅包含小写字母