返回

LeetCode373 查找和最小的 K 对数字:TopK 问题:「小根堆 & 多路归并」 |「二分 & 滑动窗口」

闲谈

LeetCode 373 查找和最小的 K 对数字

题目

给定两个以升序排列的有序数组 nums1 和 nums2,以及一个整数 k,找到并返回nums1 和 nums2 的前 k 个最小的数对。

示例

输入:nums1 = [1, 7, 11], nums2 = [2, 4, 6], k = 3
输出:[[1, 2], [1, 4], [1, 6]]
解释:前 k 个最小的数对是:[1, 2], [1, 4], [1, 6]。

算法

小根堆

小根堆是一种数据结构,其中每个节点的值都小于或等于其子节点的值。我们可以使用小根堆来存储nums1和 nums2 的前 k 个最小的数对。

  1. 将nums1和 nums2 中的所有数对添加到小根堆中。
  2. 重复以下步骤 k 次:
    • 从小根堆中弹出最小的数对。
    • 将这个数对添加到输出中。

多路归并

多路归并是一种将多个有序数组合并为一个有序数组的算法。我们可以使用多路归并来合并nums1和 nums2,然后从合并后的数组中找到前 k 个最小的数对。

  1. 将nums1和 nums2 合并为一个有序数组。
  2. 从合并后的数组中找到前 k 个最小的数对。

二分

二分是一种在有序数组中查找特定元素的算法。我们可以使用二分来查找nums1和 nums2 中的第 k 个最小的数对。

  1. 将nums1和 nums2 合并为一个有序数组。
  2. 使用二分找到合并后的数组中第 k 个最小的数对。

滑动窗口

滑动窗口是一种在数组中查找特定元素的算法。我们可以使用滑动窗口来查找nums1和 nums2 中的前 k 个最小的数对。

  1. 创建一个大小为 k 的滑动窗口,并将窗口放在nums1和 nums2 中的第一个数对上。
  2. 重复以下步骤 k 次:
    • 找到窗口中所有数对中最小的数对。
    • 将这个数对添加到输出中。
    • 将窗口向右移动一个位置。

比较

这四种算法的复杂度如下:

  • 小根堆:O((m + n) log k)
  • 多路归并:O((m + n) log (m + n))
  • 二分:O((m + n) log (m + n))
  • 滑动窗口:O(m + n)

其中,m 和 n 分别是 nums1 和 nums2 的长度。

在实践中,滑动窗口算法通常是这四种算法中最快的,因为它的复杂度与数组的长度成正比。

代码实现

import heapq

def k_smallest_pairs(nums1, nums2, k):
    """
    :type nums1: List[int]
    :type nums2: List[int]
    :type k: int
    :rtype: List[List[int]]
    """

    # 使用小根堆存储nums1和 nums2 的前 k 个最小的数对。
    heap = []
    for i in range(min(k, len(nums1))):
        for j in range(min(k, len(nums2))):
            heapq.heappush(heap, (nums1[i] + nums2[j], i, j))

    # 重复以下步骤 k 次。
    result = []
    while heap and k > 0:
        # 从小根堆中弹出最小的数对。
        _, i, j = heapq.heappop(heap)

        # 将这个数对添加到输出中。
        result.append([nums1[i], nums2[j]])

        k -= 1

        # 如果 i+1 < len(nums1),则将(nums1[i+1], nums2[j])添加到小根堆中。
        if i + 1 < len(nums1):
            heapq.heappush(heap, (nums1[i+1] + nums2[j], i+1, j))

        # 如果 j+1 < len(nums2),则将(nums1[i], nums2[j+1])添加到小根堆中。
        if j + 1 < len(nums2):
            heapq.heappush(heap, (nums1[i] + nums2[j+1], i, j+1))

    return result


def main():
    nums1 = [1, 7, 11]
    nums2 = [2, 4, 6]
    k = 3

    result = k_smallest_pairs(nums1, nums2, k)

    print(result)


if __name__ == "__main__":
    main()

总结

在这篇文章中,我们介绍了如何使用小根堆、多路归并、二分和滑动窗口算法来解决 LeetCode 373 查找和最小的 K 对数字问题。我们还比较了这四种算法的复杂度,并提供了代码实现。