leetcode
leetcode 551 ~ 600
最小区间

最小区间

难度:

标签:

题目描述

代码结果

运行时间: 107 ms, 内存: 23.4 MB


/*
  思路:
  虽然Java Stream不适合处理此问题的所有步骤,但可以用于初始化列表和获取最小值最大值等辅助操作。
  我们将使用最小堆来解决主要问题。
*/
import java.util.*;
import java.util.stream.*;
 
public class SmallestRangeStream {
    public int[] smallestRange(List<List<Integer>> nums) {
        PriorityQueue<int[]> minHeap = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
        int currentMax = nums.stream().mapToInt(list -> list.get(0)).max().orElse(Integer.MIN_VALUE);
 
        nums.stream().map(list -> new int[]{list.get(0), nums.indexOf(list), 0})
                .forEach(minHeap::offer);
 
        int minRange = Integer.MAX_VALUE;
        int start = -1;
        int end = -1;
 
        while (minHeap.size() == nums.size()) {
            int[] current = minHeap.poll();
            int currentMin = current[0];
            int listIndex = current[1];
            int elementIndex = current[2];
 
            if (currentMax - currentMin < minRange) {
                minRange = currentMax - currentMin;
                start = currentMin;
                end = currentMax;
            }
 
            if (elementIndex + 1 < nums.get(listIndex).size()) {
                int nextElement = nums.get(listIndex).get(elementIndex + 1);
                minHeap.offer(new int[]{nextElement, listIndex, elementIndex + 1});
                currentMax = Math.max(currentMax, nextElement);
            }
        }
 
        return new int[]{start, end};
    }
}

解释

方法:

该题解的思路是先将每个数字与其所在的组号绑定在一起,形成(数字,组号)的元组,然后对这些元组进行排序。接着使用滑动窗口的方法,通过左右指针维护一个窗口,保证窗口内包含了所有的组。在滑动窗口的过程中,记录下窗口大小最小的区间作为答案。具体实现时,使用哈希表 mp 来统计当前窗口内每个组出现的次数,当 mp 的大小等于总组数时,说明当前窗口已经包含了所有的组,此时更新最小区间,并尝试通过收缩左边界来缩小区间大小。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
在算法中,如何确保在初始化哈希表`mp`时,它已经包含了所有必要的组号,以便正确地追踪每个组的元素?
在该算法中,初始化哈希表`mp`并不需要预先包含所有组号。哈希表`mp`用于动态追踪窗口内每个组的元素计数。只有当某个组的元素首次出现在窗口中时,该组号才会被加入到`mp`中,并开始计数。这种动态添加的方式可以有效地处理并记录各个组的元素状态,而不需要在初始化时就包含所有组号。
🦆
对于边界情况,比如当一个或多个列表为空时,这个算法是否仍然有效?如果无效,如何修改算法以处理这种情况?
当一个或多个子列表为空时,这个算法不会有效,因为这意味着某些组没有任何元素,从而无法形成一个完整覆盖所有组的窗口。为了处理这种情况,可以在算法开始之前添加一个检查,确保所有输入的子列表都是非空的。如果发现任何一个列表为空,则可以直接返回一个错误信息或特定的输出,表明不可能找到一个包含所有组的区间。
🦆
在滑动窗口法中,为何选择对元组`(数字, 组号)`进行排序,而不是直接在原始列表上滑动?排序带来了哪些计算上的优势或必要性?
对元组`(数字, 组号)`进行排序是为了让所有来自不同组的数字按从小到大的顺序排列,这样可以更容易地通过滑动窗口来找到包含所有组的最小区间。如果直接在原始列表上滑动,由于每个列表的数字可能是无序的,且不同列表之间的数字无法有效比较,将难以实现有效的窗口滑动来覆盖所有组。排序后,可以确保窗口内的数字是连续的,并且每次扩展或收缩窗口都是有序进行,这对于追踪当前最小区间尤为重要。
🦆
在滑动窗口中,当删除哈希表`mp`中的一个元素(即当某个组在窗口中的计数为0时)后,为什么立即移动左指针`l`?这样的操作是否可能错过某些潜在的最小区间?
在滑动窗口中,左指针`l`的移动是为了尝试缩小当前区间的大小,同时保持窗口内包含所有组。当哈希表`mp`中某个组的计数变为0时,意味着该组的元素已经完全不在当前窗口内,因此需要移动左指针来寻找一个新的可能的最小区间,这可能涉及将左指针移动到新的位置以重新包含丢失的组。这种操作通常不会错过潜在的最小区间,因为一旦某个组元素完全不在窗口内,当前的窗口已经不再有效,必须调整窗口以重新满足条件。

相关问题