最小区间
描述
困难
你有 k 个升序排列的整数数组。找到一个最小区间,使得 k 个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。
示例 1:
输入:[[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
解题
目标是找一个最小区间,使得k个列表中的每个列表至少有一个数包含在其中
设置一个列表k_elements,里面包含k个数,分别是k个列表中的包含在这个区间的数
那么这个最小区间的左端点为这k个数中的最小值,右端点为这k个数中的最大值
那么只需要更新该列表就行了
例如,k_elements中的数为a_11、a_21、…、a_k1
- 下标第一个数表示属于k个列表中的哪个列表
- 下标第二个数表示在对应列表中的下标
若其中最小的数为a_i1,最大的数为a_j1,那么区间为[a_i1,a_j1]
此时最小的数为a_i1,则从原数组中取出a_i2代替a_i1,然后再更新区间左右端点
算法步骤
- 设置一个列表k_elements,其中中的元素为k个列表中的第一个值,共k个值
- 设置一个列表indices用于保存k_elements中的数据是分别是k个列表中的下标
- 设置初始left和right指针表示列表中的最小值最大值(设置需要返回的结果start和end为初始left和right)
- 每次选择k_elements中的最小的数(left)
- 如果该数已经是对应列表中的最后一个数,退出循环
- 将这个数所属列表中,这个数的后面一个数更新至这个数的位置上
- 如果更新后的数大于原k_elements中的最大的数(right),更新right
- 重新获得k_elements中的最小的数作为left
- 如果现在[left, right]区间小于[start, end],更新start和end
最后的效率可能不高,击败了8%左右的用户
from typing import List
class Solution:
def smallestRange(self, nums: List[List[int]]) -> List[int]:
k = len(nums)
# 用于保存该区间中的k个列表中的某个元素
k_elements = [nums[i][0] for i in range(k)]
# 用于保存这k个数在各自列表中的下标
indices = [0 for _ in range(k)]
# 初始区间为k个元素的最小值到最大值
start = left = min(k_elements)
end = right = max(k_elements)
length = end - start
while True:
# 获取最小值所在的数组的下标
index = k_elements.index(left)
# 如果该最小值为所在数组的最后一个值,退出
if indices[index] == len(nums[index]) - 1:
break
# 该最小值所在数组的位置后移一个位置
# 将后一个数据更新到k_elements中
indices[index] += 1
k_elements[index] = nums[index][indices[index]]
# 如果更新后的数大于右端,则更新右段指针
if k_elements[index] > right:
right = k_elements[index]
# 更新最小值
left = min(k_elements)
# 更新区间
if right - left < length:
start = left
end = right
length = right - left
return [start, end]
if __name__ == '__main__':
nums = [[4, 10, 15, 24, 26], [0, 9, 12, 20], [5, 18, 22, 30]]
print(Solution().smallestRange(nums))