2542. Maximum Subsequence Score - Explanation

Problem Link



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Heap / Priority Queue - Min-heap is used to maintain the k largest elements from nums1 efficiently
  • Sorting - Pairs are sorted by nums2 values to process elements in decreasing order of the minimum candidate
  • Greedy Algorithms - Understanding when to include/exclude elements to maximize the score
  • Two Arrays Coordination - Working with parallel arrays where indices must be processed together

1. Brute Force (Recursion)

Intuition

We need to select exactly k indices. The score is the sum of selected elements from nums1 multiplied by the minimum of selected elements from nums2. Since we must choose exactly k elements, we can use recursion to try all combinations: for each index, either include it or skip it. When we include an element, we update the running sum and track the minimum from nums2.

Algorithm

  1. Define a recursive function with parameters: current index, remaining count k, current minimum from nums2, and current sum from nums1.
  2. Base case: if k == 0, return sum * minVal.
  3. If we cannot select k more elements, return negative infinity.
  4. If minVal is 0, the result is 0 (any selection will have score 0).
  5. Try both options:
    • Skip the current index.
    • Include the current index (update min and sum).
  6. Return the maximum of both choices.
class Solution:
    def maxScore(self, nums1: List[int], nums2: List[int], k: int) -> int:
        n = len(nums1)

        def dfs(i, k, minVal, curSum):
            if k == 0:
                return curSum * minVal
            if i == n or (n - i) < k:
                return float("-inf")
            if minVal == 0:
                return 0

            res = dfs(i + 1, k, minVal, curSum)
            res = max(res, dfs(i + 1, k - 1, min(minVal, nums2[i]), curSum + nums1[i]))
            return res

        return dfs(0, k, float("inf"), 0)

Time & Space Complexity

  • Time complexity: O(2n)O(2 ^ n)
  • Space complexity: O(n)O(n) for recursion stack.

2. Min-Heap - I

Intuition

The key insight is that if we fix which element provides the minimum from nums2, we want the k largest corresponding values from nums1 (among elements with nums2 values >= our minimum). By sorting pairs by nums2 in descending order and processing them one by one, each new element becomes the new minimum. We maintain the k largest nums1 values seen so far using a min-heap.

Algorithm

  1. Create pairs of (nums1[i], nums2[i]) and sort by nums2 in descending order.
  2. Use a min-heap to track the k largest nums1 values seen so far.
  3. Maintain a running sum of elements in the heap.
  4. For each pair in sorted order:
    • Add the nums1 value to the heap and update the sum.
    • If heap size exceeds k, remove the smallest and subtract from sum.
    • If heap size equals k, calculate score (sum * current nums2) and update result.
  5. Return the maximum score.
class Solution:
    def maxScore(self, nums1: List[int], nums2: List[int], k: int) -> int:
        pairs = sorted(zip(nums1, nums2), key=lambda p: p[1], reverse=True)

        minHeap = []
        n1Sum = 0
        res = 0

        for n1, n2 in pairs:
            n1Sum += n1
            heapq.heappush(minHeap, n1)

            if len(minHeap) > k:
                n1Sum -= heapq.heappop(minHeap)

            if len(minHeap) == k:
                res = max(res, n1Sum * n2)

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

3. Min-Heap - II

Intuition

This is a space-optimized version that packs both values into a single 64-bit integer. Since the problem constraints allow values up to 10^5, we can use bit shifting to combine nums2 (upper bits) and nums1 (lower bits). Sorting these combined values gives us the same ordering as sorting by nums2, and we can extract both values using bitwise operations.

Algorithm

  1. For each index, create a combined value: (nums2[i] << 30) | nums1[i].
  2. Sort the combined array in descending order (sorts by nums2 primarily).
  3. Process each combined value:
    • Extract nums1 and nums2 using bit operations.
    • Add nums1 to heap and sum; remove smallest if heap exceeds k.
    • When heap has exactly k elements, calculate and track maximum score.
  4. Return the maximum score.
class Solution:
    def maxScore(self, nums1: List[int], nums2: List[int], k: int) -> int:
        n = len(nums1)
        arr = [(nums2[i] << 30) | nums1[i] for i in range(n)]
        arr.sort(reverse=True)

        minHeap = []
        n1Sum = 0
        res = 0

        for num in arr:
            n1, n2 = num & ((1 << 30) - 1), num >> 30
            n1Sum += n1
            heapq.heappush(minHeap, n1)

            if len(minHeap) > k:
                n1Sum -= heapq.heappop(minHeap)

            if len(minHeap) == k:
                res = max(res, n1Sum * n2)

        return res

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n)
  • Space complexity: O(n)O(n)

Common Pitfalls

Using a Max-Heap Instead of a Min-Heap

To maximize the sum of k elements from nums1, you need to keep the k largest values seen so far. This requires a min-heap so you can efficiently remove the smallest element when the heap exceeds size k. Using a max-heap makes it impossible to efficiently evict the smallest element, leading to incorrect tracking of the top k values.

Forgetting to Sort by nums2 in Descending Order

The algorithm relies on processing elements in decreasing order of nums2 values so that each new element becomes the new minimum. If you sort in ascending order or forget to sort entirely, the minimum tracking breaks and the score calculation becomes incorrect.

Not Updating the Sum When Removing from the Heap

When the heap size exceeds k and you pop the smallest element, you must also subtract that element's value from your running sum. Forgetting to update the sum after removal causes it to include values no longer in the heap, producing inflated and incorrect scores.