2542. Maximum Subsequence Score
Problem Description
In this problem, we are presented with two 0-indexed integer arrays nums1
and nums2
of the same length n
and a positive integer k
. Our objective is to find the maximum possible score by picking a subsequence of indices from nums1
with a length of k
. The score is calculated by summing up the values at the selected indices from nums1
and then multiplying the sum by the minimum value found at the corresponding indices in nums2
. In other words, if we select indices i0
, i1
, ... ik-1
, the score would be (nums1[i0] + nums1[i1] + ... + nums1[ik-1]) * min(nums2[i0], nums2[i1], ..., nums2[ik-1])
. A subsequence of indices we choose should not be confused with a subsequence of elements; we are choosing the indices themselves, which can be non-consecutive.
Intuition
To maximize the score, we intuitively want to select the k
indices that would result in the largest possible sum from nums1
while also ensuring that the minimum value selected from nums2
is as high as possible, because it multiplies the entire sum.
If we were just trying to maximize the sum of selected values from nums1
, we would simply choose the k
highest values. However, the challenge here is that we also need to consider nums2
, as its minimum value among the chosen indices will act as a multiplier for our sum from nums1
.
This leads to the strategy of pairing the elements from nums1
and nums2
and sorting these pairs in descending order based on the values from nums2
, because we are interested in larger values of nums2
due to its role as a multiplier. Now, since our final score involves a sum from nums1
and a minimum from nums2
, we wish to select the top k
indices with respect to the product of sums and minimums.
To implement this, we keep a running sum of the nums1
values and use a min heap to keep track of the k
smallest nums1
values that we have encountered. This is because, as we iterate through our sorted pairs, we want to have the ability to quickly identify and remove the smallest value from our current selection in order to replace it with a potentially better option. At each iteration, if our heap is full (i.e. has k
elements), we calculate a possible score using our running sum and the current value from nums2
. We update our maximum score if the newly calculated score exceeds our current maximum.
By following this strategy, we ensure that the eventual k-sized subsequence of indices from nums1
(with corresponding values from nums2
) will provide the maximum score.
Learn more about Greedy, Sorting and Heap (Priority Queue) patterns.
Solution Approach
The implementation of the solution involves a sort operation followed by the use of a min heap. Here is how this approach unfolds:
-
Pairing and Sorting: We start by creating pairs (
a
,b
) fromnums2
andnums1
, respectively. This allows us to process elements from both arrays simultaneously. The pairing is done using Python'szip
function, and then we sort these pairs in descending order based on the first element of each pair, which comes fromnums2
. -
Min Heap Initialization: A min heap (
q
) is a data structure that allows us to efficiently keep track of thek
smallest elements ofnums1
we have encountered so far. In Python, a min heap can be easily implemented using a list and theheapq
module's functions:heappush
andheappop
. -
Iterating Over Pairs: With our pairs sorted, we iterate over each (
a
,b
). Variablea
is fromnums2
andb
is fromnums1
. The variables
maintains a running sum of the values ofnums1
we have added to our heap so far. -
Maintaining the Heap and Calculating Scores: In each iteration, we add
b
to the heap and to the running sums
. If the heap contains more thank
elements, we remove the smallest element (which is at the root of the min heap). This ensures that our heap always contains thek
largest elements from our current range ofnums1
considered so far. We calculate the potential score by multiplying our running sums
with the minimum value fromnums2
(which isa
, because our pairs are sorted by the values fromnums2
in descending order). We update the maximum scoreans
with this potential score if it's higher than the currentans
. -
Maximizing the Score: The heap's invariant, which always maintains the largest
k
elements fromnums1
seen so far, guarantees that the running sums
is as large as it can be without considering the current pair'snums1
value,b
. Sincea
is the minimum ofnums2
for the current subsequence being considered, we get the maximum potential score for this subsequence in each iteration. By maintaining the max score throughout the iterations, we ensure we get the maximum score possible across all valid subsequences.
The implementation can be summarized by the following pseudocode:
nums = sort pairs (nums2, nums1) in descending order of nums2 values
q = initialize a min heap
s = 0 # Running sum of nums1 values in the heap
ans = 0 # Variable to store the maximum score
for each (a, b) in nums:
add b to the running sum s
push b onto the min heap q
if the size of heap q exceeds k:
remove the smallest element from q
subtract its value from the running sum s
update ans if (s * a) is greater than current ans
return ans
By traversing the sorted pairs and using a min heap to effectively manage the heap size and running sum, we ensure the efficient computation of the maximum score. This solution has a time complexity of O(n log n)
due to the sorting operation and O(n log k)
due to heap operations across n
elements, with each such operation having a time complexity of O(log k)
.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider the following example to illustrate the solution approach.
Suppose we have the following inputs:
nums1 = [3, 5, 2, 7]
nums2 = [8, 1, 4, 3]
k = 2
We want to find the maximum score by selecting k=2
indices from nums1
and using the corresponding nums2
elements as the multiplier.
-
Pairing and Sorting: First, we pair the elements from
nums1
andnums2
and sort them based onnums2
values in descending order. Pairs:[(8, 3), (4, 2), (3, 7), (1, 5)]
After sorting:[(8, 3), (4, 2), (3, 7), (1, 5)]
(already sorted in this case) -
Min Heap Initialization: We initialize an empty min heap
q
. -
Iterating Over Pairs: We iterate over the sorted pairs and maintain a running sum
s
of the elements fromnums1
that are currently in our heap. -
Maintaining the Heap and Calculating Scores:
-
First iteration (pair
(8, 3)
): Push3
to min heapq
. Nowq = [3]
and running sums = 3
. Score if this subsequence is finalized:s * a = 3 * 8 = 24
. -
Second iteration (pair
(4, 2)
): Push2
to min heapq
. Nowq = [2, 3]
and running sums = 3 + 2 = 5
. Score if this subsequence is finalized:s * a = 5 * 4 = 20
.
We don't need to remove any elements from the heap as it contains exactly k=2
elements.
-
Third iteration (pair
(3, 7)
): Although7
fromnums1
is larger, the corresponding3
fromnums2
would decrease the multiplier, so we continue without adding this pair to our heap/subsequence. -
Fourth iteration (pair
(1, 5)
): We also skip this pair as adding5
would lead to lowering the minimum value ofnums2
to1
, which isn't beneficial.
- Maximizing the Score:
Throughout the process, we keep track of the running maximum score. After going through all pairs, the maximum score is from the second iteration with a score of
20
.
Thus, the maximum score possible for the given example is 20
.
Applying this solution approach allows us to efficiently find the maximum score without examining every possible subsequence, which could be intractably large for larger arrays. By prioritizing pairs based on nums2
values (the multipliers) and keeping a running sum of the largest nums1
values, we end up with the optimal subsequence.
Solution Implementation
1from heapq import heappush, heappop
2
3class Solution:
4 def maxScore(self, cards1: List[int], cards2: List[int], k: int) -> int:
5 # Combine the two lists into one by creating tuples of cards from cards2 and cards1
6 # and sort the combined list in descending order based on cards2 values.
7 combined_cards = sorted(zip(cards2, cards1), reverse=True)
8
9 # Initialize a min-heap to keep track of the smallest elements.
10 min_heap = []
11 # Initialize sum and answer
12 current_sum = 0
13 max_score = 0
14
15 # Iterate over the sorted list of combined cards.
16 for card2_value, card1_value in combined_cards:
17 # Add the value from cards1 to the current sum.
18 current_sum += card1_value
19 # Push the value from cards1 to the min-heap.
20 heappush(min_heap, card1_value)
21
22 # If the heap size reaches k, update the maximum score.
23 # This corresponds to choosing k cards from cards1.
24 if len(min_heap) == k:
25 max_score = max(max_score, current_sum * card2_value)
26 # Remove the smallest element from the current sum as we want the largest k elements.
27 current_sum -= heappop(min_heap)
28
29 # Return the maximum possible score.
30 return max_score
31
1import java.util.Arrays;
2import java.util.PriorityQueue;
3
4class Solution {
5 public long maxScore(int[] nums1, int[] nums2, int k) {
6 // Get the length of the given arrays
7 int n = nums1.length;
8 // Initialize an array of arrays to hold pairs from nums1 and nums2
9 int[][] numsPairs = new int[n][2];
10 for (int i = 0; i < n; ++i) {
11 numsPairs[i] = new int[] {nums1[i], nums2[i]};
12 }
13
14 // Sort the pairs based on the second element in decreasing order
15 Arrays.sort(numsPairs, (a, b) -> b[1] - a[1]);
16
17 long maxScore = 0; // This will hold the maximum score
18 long sum = 0; // This will hold the sum of the smallest 'k' elements from nums1
19 // PriorityQueue to hold the smallest 'k' elements from nums1
20 PriorityQueue<Integer> minHeap = new PriorityQueue<>();
21 for (int i = 0; i < n; ++i) {
22 sum += numsPairs[i][0]; // Add the value from nums1
23 minHeap.offer(numsPairs[i][0]); // Add value to the min heap
24
25 if (minHeap.size() == k) { // If we have 'k' elements in the min heap
26 // Calculate potential score for the current combination and update maxScore if it's higher
27 maxScore = Math.max(maxScore, sum * numsPairs[i][1]);
28 // Remove the smallest value to make room for the next iteration
29 sum -= minHeap.poll();
30 }
31 }
32 // Return the calculated max score
33 return maxScore;
34 }
35}
36
1#include <vector>
2#include <queue>
3#include <algorithm>
4
5class Solution {
6public:
7 long long maxScore(std::vector<int>& nums1, std::vector<int>& nums2, int k) {
8 int n = nums1.size(); // Get the size of the input vectors
9 std::vector<std::pair<int, int>> nums(n);
10
11 // Combine the elements from nums2 and nums1 into pairs with a negative value from nums2
12 for (int i = 0; i < n; ++i) {
13 nums[i] = {-nums2[i], nums1[i]};
14 }
15
16 // Sort the vector of pairs based on the first element in non-decreasing order
17 std::sort(nums.begin(), nums.end());
18
19 // Use a min heap to keep track of the k largest elements from nums1
20 std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap;
21 long long ans = 0, sum = 0;
22
23 // Iterate over the sorted pairs
24 for (auto& [negNum2, num1] : nums) {
25 sum += num1; // Add the value from nums1 to the sum
26 minHeap.push(num1); // Push the value from nums1 into the min heap
27
28 // Once the heap size reaches k, we calculate the potential maximum score
29 if (minHeap.size() == k) {
30 // Current score is the sum times the negated value from nums2 (to make it positive again)
31 ans = std::max(ans, sum * -negNum2);
32
33 // Remove the smallest element from sum to maintain the top k largest elements
34 sum -= minHeap.top();
35 minHeap.pop(); // Remove the element from the heap
36 }
37 }
38
39 // Return the maximum score found
40 return ans;
41 }
42};
43
1// Importing the needed modules for priority queue functionality
2import { PriorityQueue } from 'typescript-collections';
3
4// Function to calculate the maximum score
5function maxScore(nums1: number[], nums2: number[], k: number): number {
6 const n: number = nums1.length; // Get the size of the input arrays
7
8 // Initialize an array of pairs
9 const nums: [number, number][] = new Array(n);
10
11 // Combine the elements from nums2 and nums1 into pairs with a negative value from nums2
12 for (let i = 0; i < n; ++i) {
13 nums[i] = [-nums2[i], nums1[i]];
14 }
15
16 // Sort the array of pairs based on the first element in non-decreasing order
17 nums.sort((a, b) => a[0] - b[0]);
18
19 // Initialize a min heap to keep track of the k largest elements from nums1
20 const minHeap = new PriorityQueue<number>((a, b) => b - a);
21 let ans: number = 0;
22 let sum: number = 0;
23
24 // Iterate over the sorted pairs
25 for (const [negNum2, num1] of nums) {
26 sum += num1; // Add the value from nums1 to the sum
27 minHeap.enqueue(num1); // Enqueue the value from nums1 onto the min heap
28
29 // Once the heap size reaches k, calculate the potential maximum score
30 if (minHeap.size() === k) {
31 // Current score is the sum times the negated value from nums2 (to make it positive again)
32 ans = Math.max(ans, sum * -negNum2);
33
34 // Remove the smallest element from sum to maintain the top k largest elements
35 sum -= minHeap.dequeue(); // Dequeue the smallest element from the min heap
36 }
37 }
38
39 // Return the maximum score found
40 return ans;
41}
42
Time and Space Complexity
Time Complexity
The given code performs several operations with distinct time complexities:
-
Sorting the combined list
nums
: This operation has a time complexity ofO(n log n)
, wheren
is the length of the combined list. Since this length is determined bynums2
, the time complexity isO(m log m)
wherem
is the length ofnums2
. -
Iterating over the sorted list
nums
: The iteration itself has a linear time complexityO(m)
. -
Pushing elements onto a heap of size
k
: Each push operation has a time complexity ofO(log k)
. Since we perform this operationm
times, the total time complexity for all push operations isO(m log k)
. -
Popping elements from the heap of size
k
: Each pop operation has a time complexity ofO(log k)
, and since a pop operation is performed each time the heap size reachesk
, this happens up tom
times. The total time complexity for all pop operations isO(m log k)
.
Combining all of these operations, the overall time complexity of the code is O(m log m + m + m log k + m log k)
. Simplifying this expression, we get the final time complexity of O(m log m + 2m log k)
, which can be approximated to O(m log(mk))
since log m
and log k
are the dominating terms.
Space Complexity
The space complexity of the code is determined by:
-
The space required for the sorted list
nums
: This isO(m)
. -
The space required for the heap
q
: In the worst case, the heap will have up tok
elements, leading to a space complexity ofO(k)
.
Therefore, the combined space complexity is O(m + k)
. Since one does not dominate the other, we represent them both in the final space complexity expression.
Learn more about how to find time and space complexity quickly using problem constraints.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
https algomonster s3 us east 2 amazonaws com cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue
Want a Structured Path to Master System Design Too? Don’t Miss This!