You are given an integer array cardPoints
and an integer k
. The array represents a row of cards, each with some points on it. Your task is to pick exactly k
cards from either the start or the end of the array (you can pick from either end in any order), and maximize the total points you can obtain from these cards.
Constraints:
k
cards in total.
Example:
Input: cardPoints = [1,2,3,4,5,6,1]
, k = 3
Output: 12
Explanation: Pick the cards with values 6, 5, and 1 (either the last three or first two and last one).
At first glance, the problem seems to ask for the maximum sum by choosing k
cards from either end of the array. A brute-force approach would be to try all possible combinations of picking cards from the start and end, but this quickly becomes inefficient as the number of combinations grows exponentially with k
.
Instead, we need an optimized method. Notice that for each way of picking cards, you can pick some cards from the front and the rest from the back, and the total number of picked cards is always k
. So, for each possible split (i.e., pick i
cards from the front and k-i
cards from the back), we can compute the sum and keep track of the maximum.
By shifting our perspective to think in terms of which cards remain unpicked, we can further optimize the solution. Since you always pick k
cards, you leave behind a contiguous subarray of length n - k
. If we find the minimum sum of any such contiguous subarray, subtracting it from the total sum gives the maximum points obtainable.
Let's break down the optimized approach step by step:
n - k
with the minimum sum.
n - k
to efficiently find the minimum sum.k
cards from the ends.Why use a sliding window? Because it lets us compute all possible sums of contiguous subarrays of a given length in linear time, which is much faster than checking every possible combination.
Let's use the sample input: cardPoints = [1,2,3,4,5,6,1]
, k = 3
.
Brute-force approach: For each possible combination of picking cards from the front and back (from 0 to k cards from the front), compute the sum. This requires O(k) computations, and each computation takes O(k) time, so overall O(k^2) time.
Optimized sliding window approach:
Here, n
is the length of cardPoints
.
The key insight is to reframe the problem: instead of focusing on which k
cards to pick, focus on which n - k
contiguous cards to leave behind. By using a sliding window to find the minimal sum of the subarray left behind, we can subtract this from the total sum to get the answer efficiently. This approach is both elegant and efficient, running in linear time and using constant space.
class Solution:
def maxScore(self, cardPoints, k):
n = len(cardPoints)
total = sum(cardPoints)
window = n - k
if window == 0:
return total
min_subarray = curr_sum = sum(cardPoints[:window])
for i in range(window, n):
curr_sum += cardPoints[i] - cardPoints[i - window]
min_subarray = min(min_subarray, curr_sum)
return total - min_subarray
class Solution {
public:
int maxScore(vector<int>& cardPoints, int k) {
int n = cardPoints.size();
int total = 0;
for (int x : cardPoints) total += x;
int window = n - k;
if (window == 0) return total;
int curr_sum = 0;
for (int i = 0; i < window; ++i)
curr_sum += cardPoints[i];
int min_subarray = curr_sum;
for (int i = window; i < n; ++i) {
curr_sum += cardPoints[i] - cardPoints[i - window];
min_subarray = min(min_subarray, curr_sum);
}
return total - min_subarray;
}
};
class Solution {
public int maxScore(int[] cardPoints, int k) {
int n = cardPoints.length;
int total = 0;
for (int x : cardPoints) total += x;
int window = n - k;
if (window == 0) return total;
int currSum = 0;
for (int i = 0; i < window; i++)
currSum += cardPoints[i];
int minSubarray = currSum;
for (int i = window; i < n; i++) {
currSum += cardPoints[i] - cardPoints[i - window];
minSubarray = Math.min(minSubarray, currSum);
}
return total - minSubarray;
}
}
var maxScore = function(cardPoints, k) {
let n = cardPoints.length;
let total = cardPoints.reduce((a, b) => a + b, 0);
let window = n - k;
if (window === 0) return total;
let currSum = 0;
for (let i = 0; i < window; i++)
currSum += cardPoints[i];
let minSubarray = currSum;
for (let i = window; i < n; i++) {
currSum += cardPoints[i] - cardPoints[i - window];
minSubarray = Math.min(minSubarray, currSum);
}
return total - minSubarray;
};