Prerequisites

Before attempting this problem, you should be comfortable with:

  • Binary Search - Using binary search to find a target value or boundary in a sorted space
  • Two Pointers Technique - Efficiently traversing sorted arrays from both ends to count elements meeting certain conditions
  • Sorted Array Properties - Leveraging the sorted nature of arrays to optimize counting and searching operations
  • Handling Negative Numbers - Understanding how multiplication sign rules affect product ordering

1. Brute Force

Intuition

The most direct approach computes all possible products by multiplying each element in the first array with each element in the second. After generating all products, we sort them and return the k-th smallest. While simple, this requires O(m * n) space and time for generation, plus O(m * n * log(m * n)) for sorting, making it impractical for large inputs.

Algorithm

  1. Create an empty list to store all products.
  2. For each element in nums1, multiply it with each element in nums2 and add to the list.
  3. Sort the product list.
  4. Return the element at index k - 1.
class Solution:
    def kthSmallestProduct(self, nums1: List[int], nums2: List[int], k: int) -> int:
        prod = []

        for i in range(len(nums1)):
            for j in range(len(nums2)):
                prod.append(nums1[i] * nums2[j])

        prod.sort()
        return prod[k - 1]

Time & Space Complexity

  • Time complexity: O(mn)O(m * n)
  • Space complexity: O(mn)O(m * n)

Where mm and nn are the lengths of the arrays nums1nums1 and nums2nums2, respectively.


Intuition

Instead of enumerating all products, we can binary search on the answer. For a candidate product value, we count how many products are less than or equal to it. If the count is less than k, we need a larger product; otherwise, we search smaller. The counting function leverages the sorted nature of both arrays: for positive numbers in nums1, we use upper bound on nums2; for negative numbers, we use lower bound with adjusted logic.

Algorithm

  1. Set the search range from -10^10 to 10^10 (product bounds).
  2. Binary search for the smallest product where at least k products are less than or equal to it.
  3. For counting products <= prod:
    • For positive a in nums1: count elements in nums2 where a * b <= prod.
    • For negative a: count elements where the product doesn't exceed prod (direction reverses).
    • For a = 0: if prod >= 0, all of nums2 contributes.
  4. Return the binary search result.
class Solution:
    def kthSmallestProduct(self, nums1: List[int], nums2: List[int], k: int) -> int:
        def count(prod):
            cnt = 0
            n2 = len(nums2)
            for a in nums1:
                if a > 0:
                    cnt += bisect.bisect_right(nums2, prod // a)
                elif a < 0:
                    threshold = math.ceil(prod / a)
                    idx = bisect.bisect_left(nums2, threshold)
                    cnt += n2 - idx
                else:
                    if prod >= 0:
                        cnt += n2
            return cnt


        l, r = -(10**10), 10**10
        while l <= r:
            m = l + (r - l) // 2
            if count(m) < k:
                l = m + 1
            else:
                r = m - 1
        return l

Time & Space Complexity

  • Time complexity: O(mlog(logN))O(m \log (\log N))
  • Space complexity: O(1)O(1)

Where mm and nn are the lengths of the arrays nums1nums1 and nums2nums2, respectively. NN is the size of the range of the product.


3. Binary Search + Two Pointers

Intuition

We can optimize the counting step using two pointers instead of binary search for each element. By separating negative and non-negative numbers in both arrays, we handle four cases: negative times negative (positive result), positive times positive, negative times positive, and positive times negative. For each case, two pointers can efficiently count products less than or equal to the target by exploiting monotonicity.

Algorithm

  1. Find the boundary indices where negative numbers end in both arrays (pos1, pos2).
  2. Binary search on the product value.
  3. For each candidate, count products using four two-pointer traversals:
    • Negatives from nums1 with negatives from nums2 (yields positives).
    • Positives from nums1 with positives from nums2.
    • Negatives from nums1 with positives from nums2 (yields negatives).
    • Positives from nums1 with negatives from nums2.
  4. Adjust pointers based on whether the current product exceeds the target.
  5. Return the smallest product where count >= k.
class Solution:
    def kthSmallestProduct(self, nums1: List[int], nums2: List[int], k: int) -> int:
        n1, n2 = len(nums1), len(nums2)
        pos1 = 0 # first non-negative in nums1
        while pos1 < n1 and nums1[pos1] < 0:
            pos1 += 1

        pos2 = 0 # first non-negative in nums2
        while pos2 < n2 and nums2[pos2] < 0:
            pos2 += 1

        def count(prod):
            cnt = 0

            # negative * negative -> positive
            i, j = 0, pos2 - 1
            while i < pos1 and j >= 0:
                if nums1[i] * nums2[j] > prod:
                    i += 1
                else:
                    cnt += (pos1 - i)
                    j -= 1

            # positive * positive -> positive
            i, j = pos1, n2 - 1
            while i < n1 and j >= pos2:
                if nums1[i] * nums2[j] > prod:
                    j -= 1
                else:
                    cnt += (j - pos2 + 1)
                    i += 1

            # negative * positive -> negative
            i, j = 0, pos2
            while i < pos1 and j < n2:
                if nums1[i] * nums2[j] > prod:
                    j += 1
                else:
                    cnt += (n2 - j)
                    i += 1

            # positive * negative → negative
            i, j = pos1, 0
            while i < n1 and j < pos2:
                if nums1[i] * nums2[j] > prod:
                    i += 1
                else:
                    cnt += (n1 - i)
                    j += 1

            return cnt

        left, right = -10**10, 10**10
        while left <= right:
            mid = (left + right) // 2
            if count(mid) < k:
                left = mid + 1
            else:
                right = mid - 1

        return left

Time & Space Complexity

  • Time complexity: O((m+n)logN)O((m + n) \log N)
  • Space complexity: O(1)O(1)

Where mm and nn are the lengths of the arrays nums1nums1 and nums2nums2, respectively. NN is the size of the range of the product.


Common Pitfalls

Integer Overflow in Product Calculations

Products of two integers can exceed the 32-bit integer range, especially when values approach 10^5 in magnitude. Always use 64-bit integers (long long in C++, long in Java) for product calculations and comparisons. Failing to do so causes incorrect counts and wrong answers.

Incorrect Handling of Negative Numbers

When one or both arrays contain negative numbers, the sign of the product flips, and the ordering reverses. A negative number multiplied by a large positive yields a more negative (smaller) result. You must handle negative-negative, negative-positive, positive-negative, and positive-positive cases separately to count products correctly.

Incorrect Floor/Ceiling Division for Negative Values

Standard integer division truncates toward zero, but counting products requires floor division for positive divisors and ceiling logic for negative divisors. Using the wrong rounding direction causes off-by-one errors in binary search bounds. Use language-specific floor division functions or manually adjust for negative operands.

Not Accounting for Zero Values

When an element is zero, its product with any number is zero. This affects counting: if the target product is non-negative, all pairs involving zero contribute to the count. Forgetting to handle zeros separately leads to missing or double-counting products.

Binary Search Boundary Errors

The search range must cover all possible products, from the most negative to the most positive. Setting bounds too tight causes the algorithm to miss the answer. Use safe bounds like -10^10 to 10^10 based on the problem constraints, and ensure the binary search condition correctly finds the smallest value with at least k products less than or equal to it.