最小区间

在力扣上刷到的一个题,感觉挺有意思,当时起手写了一个近似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

空间开销有点大,部分变量可以合并或者去掉,比如区段右边界其实可以从左边界和区间长度算出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值