每日一题:373. 查找和最小的 K 对数字(堆)
373. 查找和最小的 K 对数字
给定两个以 升序排列 的整数数组 nums1
和 nums2
, 以及一个整数 k
。
定义一对值 (u,v)
,其中第一个元素来自 nums1
,第二个元素来自 nums2
。
请找到和最小的 k
个数对 (u1,v1), (u2,v2) ... (uk,vk)
。
示例 1:
输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
输出: [1,2],[1,4],[1,6]
解释: 返回序列中的前 3 对数:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]
示例 2:
输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
输出: [1,1],[1,1]
解释: 返回序列中的前 2 对数:
[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]
示例 3:
输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]
提示:
1 <= nums1.length, nums2.length <= 1e5
-109 <= nums1[i], nums2[i] <= 1e9
nums1 和 nums2 均为升序排列
1 <= k <= 1000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-k-pairs-with-smallest-sums
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解:
方法一(k2logk):
已知nums1和nums2递增且有序,则前k小的组合一定出现在nums1[1-k]和nums2[1-k]的组合中,组合的个数为k2,然后把这k2的所有情况枚举出来,维护一个k大小的堆,这也是topk问题的解决思路。
证明:为什么一定前k小的组合一定是由nums1[1-k] 组合 nums2[1-k] 而成?
- 取nums1[k+1],nums2[k+1],显然存在[nums1[i]+nums2[i]]<[nums1[k+1]+nums2[k+1]] (i<k+1),i的取值有k这种情况,所以nums1[k+1],nums2[k+1]比不可能是前k小的组合
- 取nums1[i],nums2[j](i<k,j>k)显然存在[nums1[i]+nums2[q]]<[nums1[i]+nums2[j]] (q<j),q的取值有j-1个取值,故nums1[i],nums2[j](i<k,j>k)必不可能是前k小的组合
- nums1[i],nums2[j](i>k,j<k)情况与上相同,从而得证
代码:
自写小根堆
class Solution:
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
heap=Heap(k)
for i in nums1[:k]:
for j in nums2[:k]:
heap.add((i,j,i+j))
res=[]
for i in range(k):
max_ = heap.pop()
if max_== None:
break
res.append([max_[0],max_[1]])
res.reverse()
return res
class Heap:
def __init__(self, k):
self.heap = [(0, 0, 0)] * (k + 1)
self.size = 0
self.max_size = k
def pop(self):
if self.size==0:
return
tmp = self.heap[self.size]
self.heap[self.size] = self.heap[1]
self.size -= 1
now = 1
while now * 2 <= self.size:
a = now * 2
b = now * 2 + 1
if b > self.size:
if self.heap[a][2] > tmp[2]:
self.heap[now] = self.heap[a]
now = a
else:
break
else:
if self.heap[a][2] < self.heap[b][2]:
a = b
if self.heap[a][2] > tmp[2]:
self.heap[now] = self.heap[a]
now = a
else:
break
self.heap[now] = tmp
return self.heap[self.size + 1]
def add(self, node):
if self.size == self.max_size :
if node[2] < self.top()[2]:
self.pop()
else:
return
self.size += 1
self.heap[self.size] = node
self.adjust()
def adjust(self):
now = self.size
tmp = self.heap[now]
while now > 1:
if tmp[2] > self.heap[now // 2][2]:
self.heap[now] = self.heap[now // 2]
now = now // 2
else:
break
self.heap[now] = tmp
def top(self):
return self.heap[1]
python自带堆heapq操作
import heapq
class Solution:
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
heap=[]
for i in nums1[:k]:
for j in nums2[:k]:
heapq.heappush(heap,(i+j,[i,j]))
res=[]
for i in range(min(k,len(heap))):
max_ = heapq.heappop(heap)
if max_== None:
break
res.append(max_[1])
return res
方法二(klogk):
两个数组都是升序序列 ,所以,我们可以利用这个条件提高效率。
我们可以使用优先级队列来实现,队列中保存的是 [index1, index2],分别表示 nums1 的索引和 nums2 的索引,初始时把 [0,0]、[1, 0]、[2, 0]、…… 入队,即让 nums2 的索引全部从 0 开始,每次弹出 nums1[index1] + nums2[index2] 较小者。弹出之后,再把 index2 后移一位继续添加到优先级队列中,依次往复,最终弹出 k 次就是我们的结果。
为什么这样是可行的?
首先,利用两个数组都是升序的条件,结果中最小的组合肯定是 nums1[0] + nums2[0]。
但是,次小的是什么呢? 是 nums1[0] + nums2[1] 还是 nums1[1] + nums2[0] 呢?我们不知道。
所以,我们需要设计一种比较方式使我们能知道上述比较的结果,使用优先级队列就可以解决。
假设,我们以 [0, 1] 表示 nums1[0] + nums2[1] 的结果,整个过程的处理如下:
先把所有的 nums1 的索引入队,即入队的元素有 [0, 0]、[1, 0]、[2, 0]、[3, 0]、…
首次弹出的肯定是 [0, 0],再把 [0, 1] 入队;
这样就可以通过优先级队列比较 [0, 1] 和 [1, 0] 的结果,再弹出较小者;
依次进行,进行 k 轮。
作者:tong-zhu
链接:https://leetcode-cn.com/problems/find-k-pairs-with-smallest-sums/solution/tong-ge-lai-shua-ti-la-you-xian-ji-dui-l-fw7y/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution:
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
heap = Heap(k * 2)
i = 0
for j in range(min(k, len(nums2))):
heap.add((i, j, nums1[i] + nums2[j]))
res = []
for i in range(k):
# print(heap.heap)
min_ = heap.pop()
if min_==None:
break
res.append([nums1[min_[0]], nums2[min_[1]]])
if min_[0] + 1 >= len(nums1) and min_[1] + 1 >= len(nums2):
continue
elif min_[0] + 1 >= len(nums1):
heap.add((min_[0], min_[1] + 1, nums1[min_[0]] + nums2[min_[1] + 1]))
elif min_[1] + 1 >= len(nums2):
heap.add((min_[0] + 1, min_[1], nums1[min_[0] + 1] + nums2[min_[1]]))
else:
heap.add((min_[0] + 1, min_[1], nums1[min_[0] + 1] + nums2[min_[1]]))
heap.add((min_[0], min_[1] + 1, nums1[min_[0]] + nums2[min_[1] + 1]))
return res
class Heap:
def __init__(self, k):
self.heap = [(0, 0, 0)] * (k + 1)
self.size = 0
self.max_size = k
self.visited = set([])
def pop(self):
if self.size == 0:
return
tmp = self.heap[self.size]
self.heap[self.size] = self.heap[1]
self.size -= 1
now = 1
while now * 2 <= self.size:
a = now * 2
b = now * 2 + 1
if b > self.size:
if self.heap[a][2] < tmp[2]:
self.heap[now] = self.heap[a]
now = a
else:
break
else:
if self.heap[a][2] > self.heap[b][2]:
a = b
if self.heap[a][2] < tmp[2]:
self.heap[now] = self.heap[a]
now = a
else:
break
self.heap[now] = tmp
return self.heap[self.size + 1]
def add(self, node):
# if self.size == self.max_size :
# self.pop()
if node in self.visited:
return
self.visited.add(node)
self.size += 1
self.heap[self.size] = node
self.adjust()
def adjust(self):
now = self.size
tmp = self.heap[now]
while now > 1:
if tmp[2] < self.heap[now // 2][2]:
self.heap[now] = self.heap[now // 2]
now = now // 2
else:
break
self.heap[now] = tmp
def top(self):
return self.heap[1]