leetcode
leetcode 2851 ~ 2900
最短超串

最短超串

难度:

标签:

题目描述

You are given two arrays, one shorter (with all distinct elements) and one longer. Find the shortest subarray in the longer array that contains all the elements in the shorter array. The items can appear in any order.

Return the indexes of the leftmost and the rightmost elements of the array. If there are more than one answer, return the one that has the smallest left index. If there is no answer, return an empty array.

Example 1:

Input:
big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7]
small = [1,5,9]
Output: [7,10]

Example 2:

Input:
big = [1,2,3]
small = [4]
Output: []

Note:

  • big.length <= 100000
  • 1 <= small.length <= 100000

代码结果

运行时间: 73 ms, 内存: 30.2 MB


/*
 * Problem: Given two arrays, one large and one small, where the elements of the small array are all unique. 
 * Find the shortest subarray in the large array that contains all elements of the small array, regardless of their order.
 * Return the left and right indices of the shortest subarray. If there are multiple such subarrays, return the one with the smallest left index.
 * If no such subarray exists, return an empty array.
 *
 * Example 1:
 * Input:
 * big = [7,5,9,0,2,1,3,5,7,9,1,1,5,8,8,9,7]
 * small = [1,5,9]
 * Output: [7,10]
 *
 * Example 2:
 * Input:
 * big = [1,2,3]
 * small = [4]
 * Output: []
 */

import java.util.*;
import java.util.stream.Collectors;

public class SolutionStream {
    public int[] shortestSeq(int[] big, int[] small) {
        Set<Integer> smallSet = Arrays.stream(small).boxed().collect(Collectors.toSet());
        Map<Integer, Long> countMap = Arrays.stream(small).boxed().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
        Map<Integer, Long> windowMap = new HashMap<>();
        int left = 0, minLeft = 0, minRight = Integer.MAX_VALUE;
        for (int right = 0; right < big.length; right++) {
            if (smallSet.contains(big[right])) {
                windowMap.put(big[right], windowMap.getOrDefault(big[right], 0L) + 1);
            }
            while (windowMap.size() == countMap.size() && windowMap.entrySet().containsAll(countMap.entrySet())) {
                if (right - left < minRight - minLeft) {
                    minLeft = left;
                    minRight = right;
                }
                if (smallSet.contains(big[left])) {
                    windowMap.put(big[left], windowMap.get(big[left]) - 1);
                    if (windowMap.get(big[left]) == 0) windowMap.remove(big[left]);
                }
                left++;
            }
        }
        return minRight == Integer.MAX_VALUE ? new int[]{} : new int[]{minLeft, minRight};
    }
}

解释

方法:

本题解采用了滑动窗口的策略来找到包含所有'small'数组元素的最短子数组。首先,使用字典'cnt'来记录'small'中每个元素的计数,此时每个元素的计数初始化为1。接着,定义两个指针'left'和'right'表示当前窗口的左右端点,并使用变量'need'来表示还需包含的'small'数组的元素数。通过移动'right'指针扩展窗口,并在每次移动时更新'cnt'和'need'。当'need'为0时,说明窗口已包含所有'small'的元素,此时尝试通过移动'left'指针缩小窗口以寻找更短的子数组。每次更新窗口时都检查并更新最小长度的子数组。如果在整个过程中找到了符合条件的子数组,则记录其起始和结束位置。

时间复杂度:

O(n)

空间复杂度:

O(m)

代码细节讲解

🦆
在初始化`cnt`字典时,为什么选择将`small`数组中的每个元素计数初始化为1?
在`cnt`字典中,每个元素的计数初始化为1是为了表示每个元素最初都需要在`big`数组中被找到一次来满足条件。这个计数随着在`big`数组中找到相应元素而递减,直到0表示该元素的需求已经被满足。此外,初始化为1有助于通过`need`变量追踪还需要多少不同的元素才能满足条件,每发现一个新元素,`need`就减1,从而控制何时开始尝试缩小窗口求解最小长度子数组。
🦆
如果在`big`数组中某个元素的计数已经是负数,为什么仍然减少`need`值?
在`big`数组中某个元素的计数如果已经是负数,这表示该元素在当前窗口内出现的次数已经超过了`small`数组中的需求次数。在这种情况下,减少`need`值并不是由于找到了未满足需求的新元素,而是在首次遇到该元素时已经减少了`need`,后续即便出现计数为负的情况,也不应再次修改`need`。因此,实际代码中仅在`cnt[num] > 0`时,即这是首次或者是满足需求的情况下才减少`need`。如果计数为负,说明元素已经过量,不影响`need`的值。
🦆
在缩小窗口的过程中,为什么要检查`cnt[big[left]] >= 0`才增加`need`,而不是直接增加?
在缩小窗口时检查`cnt[big[left]] >= 0`是为了确定减少窗口左边界时是否会导致某个在`small`数组中的元素不再满足需求条件。如果`cnt[big[left]] >= 0`,这意味着移除这个元素后,它在窗口中的数量将少于`small`数组中的需求数量,因此需要增加`need`,表示现在需要再次找到这个元素。如果`cnt[big[left]]`小于0,则表示即使减去这个元素,窗口中仍然有足够数量的该元素满足`small`的需求,因此不需要增加`need`。

相关问题