SWE · VO Coding

LinkedIn 两正序数组最小 K 个乘积对:有序行小根堆

把乘积矩阵看作每行升序序列,堆合并取前 K,并解释正数、重复值与稳定平局前提。

题目重点:把乘积矩阵看作每行升序序列,堆合并取前 K,并解释正数、重复值与稳定平局前提。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练输入两个非降序正整数数组,返回 `(product,i,j)`,按乘积、i、j 排序;K≥0,K 超过总 pair 数时返回全部。题目只说 positive,若允许 0 仍单调但训练版拒绝以保持约束清晰。
  • 同值不同索引视作不同 pair,不能按乘积去重。

解题路线

  1. 对 A 的每个 i,序列 A[i]×B[0], A[i]×B[1],… 因两数组正且 B 升序而非降;把每行首项放入 min heap。
  2. 每次弹出全局最小 (product,i,j) 并输出,然后把同行下一个 j+1 放回;始终每行只暴露最小未输出项,故堆顶为所有剩余 pair 的最小值。
  3. 只需初始化前 min(len(A),K) 行:第 i≥K 行的首项不可能在严格稳定按 (product,i,j) 的前 K 之外挤掉前 K 行各自首项;若实现不想证明,可初始化所有行,复杂度更直观。
  4. 负数会使每行乘积顺序反转,必须重新设计行方向/分组;不能将正数约束静默删除。

手动走一遍

A=[1,2]、B=[2,3,4],乘积 pair 前四项按稳定索引规则为 (2,0,0)、(3,0,1)、(4,0,2)、(4,1,0)。两个乘积 4 均保留。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

import heapq


def k_smallest_products(a: list[int], b: list[int], k: int) -> list[tuple[int, int, int]]:
    """Return (product, i, j), ordered by product then source indices."""
    if k < 0 or any(x <= 0 for x in a + b):
        raise ValueError("nonnegative k and positive values required")
    if not a or not b or k == 0:
        return []
    if any(a[i] > a[i + 1] for i in range(len(a) - 1)) or any(b[j] > b[j + 1] for j in range(len(b) - 1)):
        raise ValueError("sorted arrays required")
    heap = [(a[i] * b[0], i, 0) for i in range(min(len(a), k))]
    heapq.heapify(heap)
    result = []
    for _ in range(min(k, len(a) * len(b))):
        product, i, j = heapq.heappop(heap)
        result.append((product, i, j))
        if j + 1 < len(b):
            heapq.heappush(heap, (a[i] * b[j + 1], i, j + 1))
    return result

复杂度

N=len(A)、M=len(B)、R=min(N,K):建堆 O(R),输出最多 K 项每项 O(log R),总 O(R+K log R),堆 O(R)、结果 O(K)。训练代码 20,000 组随机网格与全笛卡尔积排序 oracle 对照。

易错点与追问

容易失分的边界

  • 把乘积相同的不同 pair 去重。
  • 允许负数却仍假设每行升序。
  • 只把 (0,0) 放堆,每次只右移/下移但无 visited,重复/漏 pair。
  • K 大于 N×M 时越界。
  • 把最小数对和最小乘积对混淆。

继续深挖

  • 若数组允许零或负数?
  • 只返回乘积不返回索引时重复如何定义?
  • K 接近 N×M 时直接排序是否更简单?
  • 数组在磁盘上时怎样批量顺序读?
✓已复制微信号!