两个有序数组的第 K 小乘积

标签: 数组 二分查找

难度: Hard

给你两个 从小到大排好序 且下标从 0 开始的整数数组 nums1 和 nums2 以及一个整数 k ,请你返回第 k (从 1 开始编号)小的 nums1[i] * nums2[j] 的乘积,其中 0 <= i < nums1.length 0 <= j < nums2.length 。

示例 1:

输入:nums1 = [2,5], nums2 = [3,4], k = 2
输出:8
解释:第 2 小的乘积计算如下:
- nums1[0] * nums2[0] = 2 * 3 = 6
- nums1[0] * nums2[1] = 2 * 4 = 8
第 2 小的乘积为 8 。

示例 2:

输入:nums1 = [-4,-2,0,3], nums2 = [2,4], k = 6
输出:0
解释:第 6 小的乘积计算如下:
- nums1[0] * nums2[1] = (-4) * 4 = -16
- nums1[0] * nums2[0] = (-4) * 2 = -8
- nums1[1] * nums2[1] = (-2) * 4 = -8
- nums1[1] * nums2[0] = (-2) * 2 = -4
- nums1[2] * nums2[0] = 0 * 2 = 0
- nums1[2] * nums2[1] = 0 * 4 = 0
第 6 小的乘积为 0 。

示例 3:

输入:nums1 = [-2,-1,0,1,2], nums2 = [-3,-1,2,4,5], k = 3
输出:-6
解释:第 3 小的乘积计算如下:
- nums1[0] * nums2[4] = (-2) * 5 = -10
- nums1[0] * nums2[3] = (-2) * 4 = -8
- nums1[4] * nums2[0] = 2 * (-3) = -6
第 3 小的乘积为 -6 。

提示:

  • 1 <= nums1.length, nums2.length <= 5 * 104
  • -105 <= nums1[i], nums2[j] <= 105
  • 1 <= k <= nums1.length * nums2.length
  • nums1 和 nums2 都是从小到大排好序的。

Submission

运行时间: 416 ms

内存: 44.3 MB

from numpy import array, sum

class Solution:
    def kthSmallestProduct(self, nums1: List[int], nums2: List[int], k: int) -> int:
        nums1, nums2 = array(nums1), array(nums2)
        LEN2 = nums2.size
        above = nums1[nums1>0]
        below = nums1[nums1<0]
        zeroCount = sum(nums1 == 0)
        minmax = [nums1[0]*nums2[0], nums1[0]*nums2[-1], nums1[-1]*nums2[0], nums1[-1]*nums2[-1]]
        left, right = int(min(minmax)), int(max(minmax)+1)
        while (left < right):
            n = (left+right)>>1
            if (sum(nums2.searchsorted(n/above)) + 
                LEN2*below.size-sum(nums2.searchsorted(n/below, "right")) + 
                (zeroCount*LEN2 if n>0 else 0)) < k:
                left = n+1
            else:
                right = n
        return left-1

print(Solution().kthSmallestProduct([randint(1, 2*10**5) for i in range(2*10**5)], [randint(1, 2*10**5) for i in range(10**5)], 2*10**10))

Explain

The solution utilizes a binary search method on the product values rather than the indices of the arrays. Given two sorted arrays, the smallest and largest possible products are first identified. These products form the range for the binary search. The binary search's middle value is then used to count the products that are smaller than this middle value, by checking how many elements from nums2 are less than the division of this middle value by each element in nums1. This count determines if the k-th smallest product is greater or smaller than the middle value, adjusting the search range accordingly. The process continues until the exact k-th smallest product is located.

时间复杂度: O(log(maxProduct - minProduct) * log(len(nums2)) * len(nums1))

空间复杂度: O(len(nums1) + len(nums2))

from numpy import array, sum

class Solution:
    def kthSmallestProduct(self, nums1: List[int], nums2: List[int], k: int) -> int:
        nums1, nums2 = array(nums1), array(nums2)  # Convert lists to numpy arrays
        LEN2 = nums2.size  # Length of the second array
        above = nums1[nums1 > 0]  # Subarray of positive elements in nums1
        below = nums1[nums1 < 0]  # Subarray of negative elements in nums1
        zeroCount = sum(nums1 == 0)  # Count of zeros in nums1
        # Calculate potential minimum and maximum products from boundary elements
        minmax = [nums1[0] * nums2[0], nums1[0] * nums2[-1], nums1[-1] * nums2[0], nums1[-1] * nums2[-1]]
        left, right = int(min(minmax)), int(max(minmax) + 1)  # Set initial binary search bounds
        while (left < right):  # Binary search loop
            n = (left + right) >> 1  # Middle product value
            if (sum(nums2.searchsorted(n / above)) +  # Count of products less than 'n' with positive nums1 elements
                LEN2 * below.size - sum(nums2.searchsorted(n / below, "right")) +  # Adjust count for negative nums1 elements
                (zeroCount * LEN2 if n > 0 else 0)) < k:  # Include zero product counts if 'n' allows
                left = n + 1  # Adjust search range
            else:
                right = n
        return left - 1  # Since left is incremented one past the correct answer

Explore

直接生成所有可能的乘积然后排序的方法涉及到的计算和空间复杂度较高,特别是当输入数组较大时。这种方法需要O(m*n)的时间来生成所有乘积,其中m和n是两个数组的长度,并需要O(m*n)的空间来存储这些乘积,之后还需要O(m*n log(m*n))的时间来排序这些乘积。相比之下,二分查找法在产品值上的复杂度通常为O(log(max_product-min_product) * (m+n)),这在两个数组长度较大,或者k值较小的情况下,可以显著减少计算量。此外,二分查找法只需要常数空间,更适合处理大数据量的问题。

在二分查找法中,我们首先确定乘积可能的最小值和最大值作为搜索的边界。然后计算中间值`n`(即当前最小和最大乘积的平均值)。对于中间值`n`,我们需要计算有多少对(i, j)的乘积小于或等于`n`。这可以通过遍历数组`nums1`中的每个元素,然后对于每个元素,使用二分搜索确定在`nums2`中有多少元素与其乘积不超过`n`。具体来说,对于`nums1`中的正数,查找`nums2`中小于等于`n / element`的元素数量;对于负数,查找大于等于`n / element`的元素数量。通过这种方式,我们可以计算出总的符合条件的乘积数量。如果这个数量小于k,那么说明第k小的乘积大于`n`,调整搜索范围;如果这个数量大于或等于k,调整搜索范围至更小的乘积范围。重复这一过程直至找到确切的第k小乘积。

`numpy`库的`searchsorted`方法用于在已排序的数组中找到一个数值应当插入的位置,以保持数组的有序性。在这个问题中,它被用来快速确定在`nums2`中有多少个元素与`nums1`中的某个元素相乘的结果不超过中间值`n`。对于`nums1`中的每个正数元素,使用`searchsorted`找出`nums2`中小于等于`n / element`的元素的个数;对于每个负数元素,使用`searchsorted`(使用'right'模式)找出大于等于`n / element`的元素的个数。这种方法允许我们以对数时间复杂度快速计算出不超过`n`的乘积数量,从而有效支持二分查找过程。