在力扣上刷到的一个题,感觉挺有意思,当时起手写了一个近似O(kn²)时间复杂的代码,但提交上去发现果然超时。最后借鉴了另一个题“最小覆盖子串”的思路,在原来的代码上进行了修改,将时间复杂度优化为O(n)。
题目
你有 k 个升序排列的整数列表。找到一个最小区间,使得 k 个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。
示例:
输入:[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
输出:[20,24]
解释:
列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。
列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。
列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。
提示:
给定的列表可能包含重复元素,所以在这里升序表示 >= 。
1 <= k <= 3500
-105 <= 元素的值 <= 105
对于使用Java的用户,请注意传入类型已修改为List<List<Integer>>。重置代码模板后可以看到这项改动。
思路
大致思路是:先将所有的k个数组合并,为了知道每个数字来自哪个数组,给每个数(i)配一个标识(j),表示对应的数组编号,可以用元组列表实现,将该元组列表按i进行升序排序,这样问题变成了类似的“最小覆盖”问题,我们的目的就是要找到两个数i1和i2,使得他们之间的数包含了所有的标识(j)0~k-1,并且i2-i1要最小;
采用“滑动窗口”法,建立一个哈希表存储当前窗口中包含的标识0~k-1的个数,从最左边开始,右边界每次往移右一格,并把它对应的j的个数在哈希表中+1,知道当前窗口包含了0~k-1中的每个数至少一次,此时开始移动左边界,左边界每次往右移一格,并且检查区间是否更小,如果更小,更新区间和区间长度,知道当前窗口中没有再包含0~k-1。
关键点
1、如果在检查窗口中是否包含0~k-1时采用遍历法,那么每次都需要花费额外的O(k)的时间,但其实我们只需要维护一个变量来表示当前窗口中包含的0~k-1的个数即可,这样每次检查都能在O(1)的时间内完成
2、在排序过程中,可以直接用自带的排序方法进行排序,事实证明也能通过,但其实由于原来的k个数组是已经排好序的,所以,我们可以用“分组排序”的思想,直接进行比较后一一放入,这样排序过程也能在O(n)的时间内完成
3、在维护“检查变量”时,我们只关注哈希表中的个数是否大于等于1,所以我们只在当某个标识超过0或者<1时改变该变量
源代码
Python版本:
import sys
class Solution:
def __sort(self, nums): # 可以自己手动排序
pass
def smallestRange(self, nums: List[List[int]]) -> List[int]:
min_length = sys.maxsize # 最小区间长度
fragment_range = [0, 0] # 最小区间
n = len(nums)
num_tuple = [(i, j) for j in range(n) for i in nums[j]] # 元组列表[数字,数组编号]
num_tuple.sort(key=lambda x:x[0]) # 排序
num_dict = {i:0 for i in range(n)} # 保存每个标识的个数
distance = 0 # 包含0~k-1中的个数
left = 0
right = 0
while right < len(num_tuple):
num_dict[num_tuple[right][1]] += 1
if num_dict[num_tuple[right][1]] == 1:
distance += 1
while distance == n: # 满足条件
if num_tuple[right][0] - num_tuple[left][0] < min_length: # 更新
fragment_range[0] = num_tuple[left][0]
fragment_range[1] = num_tuple[right][0]
min_length = num_tuple[right][0] - num_tuple[left][0]
num_dict[num_tuple[left][1]] -= 1
if num_dict[num_tuple[left][1]] < 1:
distance -= 1
left += 1
right += 1
return fragment_range
空间开销有点大,部分变量可以合并或者去掉,比如区段右边界其实可以从左边界和区间长度算出来。