leetcode
leetcode 601 ~ 650
最小窗口子序列

最小窗口子序列

难度:

标签:

题目描述

代码结果

运行时间: 92 ms, 内存: 16.0 MB


/*
 * 最小窗口子序列
 *
 * 思路:
 * 1. 使用流来遍历字符串 S 和 T。
 * 2. 当找到匹配的子序列时,记录其长度,并尝试找到更小的匹配子序列。
 * 3. 最终返回最小长度的子序列。
 */
 
import java.util.Optional;
import java.util.stream.IntStream;
 
public class Solution {
    public String minWindow(String S, String T) {
        int sLen = S.length(), tLen = T.length();
        int[] minLength = {Integer.MAX_VALUE};
        String[] result = {""};
 
        IntStream.range(0, sLen).filter(i -> S.charAt(i) == T.charAt(0)).forEach(i -> {
            int sIndex = i, tIndex = 0;
            while (sIndex < sLen && tIndex < tLen) {
                if (S.charAt(sIndex) == T.charAt(tIndex)) {
                    tIndex++;
                }
                sIndex++;
            }
            if (tIndex == tLen) {  // 完全匹配
                int end = sIndex;
                sIndex--; tIndex--;
                while (tIndex >= 0) {  // 反向收缩匹配子序列
                    if (S.charAt(sIndex) == T.charAt(tIndex)) {
                        tIndex--;
                    }
                    sIndex--;
                }
                sIndex++;
                if (end - sIndex < minLength[0]) {  // 更新最小长度和结果
                    minLength[0] = end - sIndex;
                    result[0] = S.substring(sIndex, end);
                }
            }
        });
 
        return result[0];
    }
}
 

解释

方法:

题解采用的是双指针方法,首先使用两个指针 index_s1 和 index_s2 分别遍历 s1 和 s2。index_s1 用于在 s1 中寻找包含 s2 的子序列,而 index_s2 用于确保子序列中的字符顺序与 s2 相同。当 index_s2 等于 s2 的长度时,意味着找到了一个包含 s2 的子序列。接下来,通过向左移动 start 指针来缩短找到的子序列,直到它不再包含 s2 的所有字符。这样可以确保找到的子序列是最短的。最后,更新最小子序列和其长度,然后重置 index_s2 为 0 并将 index_s1 设置为 start,以便寻找下一个可能的子序列。

时间复杂度:

O(m * n)

空间复杂度:

O(1)

代码细节讲解

🦆
在算法中,如何确保`index_s2`在减少的过程中不会错过s2中的任何字符,特别是当s2中有重复字符时?
在算法中,`index_s2`的减少过程是伴随着s1中的`start`指针左移来实现的。每次左移时,只有当s1中的字符与s2中`index_s2`指向的字符相匹配时,`index_s2`才会减少。这个过程确保了从s1的当前子序列的末端开始向前搜索,匹配s2的每一个字符,直到找到与s2第一个字符相匹配的s1中的字符。因为这一过程是严格按照s2中字符的顺序逆向进行的,所以即使s2中有重复字符,也能保证每个字符都被正确匹配,不会错过。
🦆
为什么在找到满足条件的子序列后需要从后向前搜索以缩短子序列长度?这样做的具体目的是什么?
找到包含s2的子序列后,从后向前搜索可以帮助寻找该子序列的最小可能长度。这个步骤是为了确保子序列不仅包含s2,而且是最短的,因为题目要求的是最小窗口子序列。通过从后向前缩减,我们可以去除子序列开始部分的不必要字符,这些字符不影响子序列包含s2的完整性,从而达到缩短整个子序列长度的目的。
🦆
当`index_s1`重新设置为`start`后,为什么不会错过其他可能的最小窗口子序列?
将`index_s1`重新设置为`start`是基于当前找到的子序列已经是以s1中`start`位置开始的最短子序列这一事实。这样做允许算法在下一次迭代中寻找以`s1[start+1]`开始的新子序列,从而可能找到一个更短的符合条件的子序列。此操作保证了不会错过更短的子序列,因为它从上一次找到的子序列的起始位置后的下一个字符重新开始搜索。
🦆
算法描述中提到,当找到一个满足条件的子序列后,会重新设置`index_s2`为0并将`index_s1`设置为`start`。这种方式是否可能导致算法效率低下,因为它可能会重复检查之前已经检查过的字符?
虽然这种方式可能导致算法在某些情况下重复检查之前已经检查过的字符,但这是为了确保不错过更短的子序列。重新设置`index_s2`和`index_s1`是必要的步骤,因为每次找到一个符合条件的子序列后,需要从这个子序列的起始位置的下一个字符开始,重新检查是否存在更短的子序列。这种方法虽然可能在某种程度上降低了效率,但是它是实现确保找到最短子序列的必须过程。为了优化效率,可以考虑使用更高效的数据结构或算法优化策略。

相关问题

最小覆盖子串

给你一个字符串 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) 时间内解决此问题的算法吗?

最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 lrl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

 

示例 1:

输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 

示例 2:

输入:nums = [2,2,2,2,2]
输出:1
解释:最长连续递增序列是 [2], 长度为1。

 

提示:

  • 1 <= nums.length <= 104
  • -109 <= nums[i] <= 109