Given an integer array cardPoints and an integer k, you have to pick exactly k cards.
In one move, you can pick a card either from the beginning or from the end of the array.
Return the maximum score you can obtain after picking exactly k cards.
Example 1
Input: cardPoints = [1, 2, 3, 4, 5, 6, 1], k = 3
Output: 12
Explanation: We can pick 6 and 1 from the end, and 5 from the end before them. The selected cards are 5, 6, and 1. Their total score is 12.
Example 2
Input: cardPoints = [2, 2, 2], k = 2
Output: 4
Explanation: Any two cards can be picked, and the maximum score will be 4.
Example 3
Input: cardPoints = [9, 7, 7, 9, 7, 7, 9], k = 7
Output: 55
Explanation: Since k is equal to the number of cards, all cards are picked. The total score is 55.
Brute Force Approach
Every valid selection contains some cards from the left end and the remaining cards from the right end. For k selections, the number of left cards can range from 0 to k.
Each possible split can be evaluated independently by recalculating the corresponding left and right sums. Complete split examination guarantees the maximum score, but repeated summation increases the running time.
Algorithm
Store the array size in n and return -1 when
k < 0ork > n.Return 0 when k equals 0, and return the total array sum when k equals n.
Initialize maxScore with the smallest possible value to support negative card values.
Traverse leftCount from 0 to k and calculate
rightCount = k - leftCount.Add the first leftCount values and the final rightCount values to obtain currentScore.
Update maxScore with the larger value between maxScore and currentScore, then return maxScore after checking every split.
Dry Run
Maximum Points You Can Obtain from Cards Brute Force Dry Run.png
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Finds the maximum score by evaluating every left-right split. int maxScore(vector<int>& cardPoints, int k) { int n = cardPoints.size(); // Invalid k cannot form a valid selection. if (k < 0 || k > n) { return -1; } // Picking no cards gives a score of zero. if (k == 0) { return 0; } // Picking all cards requires the complete array sum. if (k == n) { return accumulate(cardPoints.begin(), cardPoints.end(), 0); } int maxScore = INT_MIN; // Try every possible number of cards taken from the left. for (int leftCount = 0; leftCount <= k; leftCount++) { int rightCount = k - leftCount; int currentScore = 0; // Add the selected cards from the left end. for (int i = 0; i < leftCount; i++) { currentScore += cardPoints[i]; } // Add the remaining selected cards from the right end. for (int i = 0; i < rightCount; i++) { currentScore += cardPoints[n - 1 - i]; } // Keep the best score among all valid splits. if (currentScore > maxScore) { maxScore = currentScore; } } return maxScore; }};int main() { vector<int> cardPoints = {1, 2, 3, 4, 5, 6, 1}; int k = 3; Solution solution; cout << solution.maxScore(cardPoints, k) << endl; return 0;}Complexity Analysis
Time Complexity: O(K²), because K + 1 splits receive examination and up to K selected values may require summation for every split.
Space Complexity: O(1), because only counters and sum variables require auxiliary storage.
Better Approach
Repeated summation can be removed by precomputing scores for every possible number of cards selected from each end.
Array leftSum stores sums of the first i cards, while rightSum stores sums of the final i cards. Any split score can then be obtained in constant time by combining one prefix value and one suffix value.
Algorithm
Store the array size in n and return -1 when
k < 0ork > n.Return 0 when k equals 0, and return the total array sum when k equals n.
Create leftSum and rightSum with size
k + 1, with position 0 initialized to 0.Build
leftSum[i]using the first i cards andrightSum[i]using the final i cards.Traverse leftCount from 0 to k, calculate
rightCount = k - leftCount, and obtaincurrentScore = leftSum[leftCount] + rightSum[rightCount].Update maxScore for every split and return maxScore after complete traversal.
Dry Run
Maximum Points You Can Obtain from Cards Better Appraoch Dry Run.png
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Finds the maximum score using precomputed left and right sums. int maxScore(vector<int>& cardPoints, int k) { int n = cardPoints.size(); // Invalid k cannot form a valid selection. if (k < 0 || k > n) { return -1; } // Picking no cards gives a score of zero. if (k == 0) { return 0; } // Picking all cards requires the complete array sum. if (k == n) { return accumulate(cardPoints.begin(), cardPoints.end(), 0); } vector<int> leftSum(k + 1, 0); vector<int> rightSum(k + 1, 0); // Store sums for every possible number of left cards. for (int i = 1; i <= k; i++) { leftSum[i] = leftSum[i - 1] + cardPoints[i - 1]; } // Store sums for every possible number of right cards. for (int i = 1; i <= k; i++) { rightSum[i] = rightSum[i - 1] + cardPoints[n - i]; } int maxScore = INT_MIN; // Combine each valid left count with its matching right count. for (int leftCount = 0; leftCount <= k; leftCount++) { int rightCount = k - leftCount; int currentScore = leftSum[leftCount] + rightSum[rightCount]; // Keep the maximum score among all possible splits. if (currentScore > maxScore) { maxScore = currentScore; } } return maxScore; }};int main() { vector<int> cardPoints = {1, 2, 3, 4, 5, 6, 1}; int k = 3; Solution solution; cout << solution.maxScore(cardPoints, k) << endl; return 0;}Complexity Analysis
Time Complexity: O(K), because construction of both sum arrays and examination of all K + 1 splits require linear time.
Space Complexity: O(K), because leftSum and rightSum each contain K + 1 values.
Optimal Approach
Selecting the first k cards forms one valid split. Replacing one selected left card with one card from the right creates the next possible split.
Every replacement removes the rightmost card from the current left selection and adds the next available card from the right end. Repeating the replacement k times generates all possible left-right distributions without extra arrays.
Algorithm
Store the array size in n and return -1 when
k < 0ork > n.Return 0 when k equals 0, and return the total array sum when k equals n.
Calculate the sum of the first k cards and store the result in currentScore.
Initialize maxScore with currentScore and rightIndex with
n - 1.Traverse
leftIndexfromk - 1down to0. During each step, subtractcardPoints[leftIndex]because that card leaves the left selection, addcardPoints[rightIndex]because the next card from the right enters the selection, then decrementrightIndex.Update maxScore after every replacement, then return maxScore after generating all possible splits.
Dry Run
Maximum Points You Can Obtain from Cards Optimal Appraoch Dry Run.png
Solution
#include <bits/stdc++.h>using namespace std;class Solution {public: // Finds the maximum score by shifting selections between both ends. int maxScore(vector<int>& cardPoints, int k) { int n = cardPoints.size(); // Invalid k cannot form a valid selection. if (k < 0 || k > n) { return -1; } // Picking no cards gives a score of zero. if (k == 0) { return 0; } // Picking all cards requires the complete array sum. if (k == n) { return accumulate(cardPoints.begin(), cardPoints.end(), 0); } int currentScore = 0; // Start with all k selected cards taken from the left. for (int i = 0; i < k; i++) { currentScore += cardPoints[i]; } int maxScore = currentScore; int rightIndex = n - 1; // Replace one left card with one right card in each step. for (int leftIndex = k - 1; leftIndex >= 0; leftIndex--) { currentScore -= cardPoints[leftIndex]; currentScore += cardPoints[rightIndex]; rightIndex--; // Keep the best score after each new split. if (currentScore > maxScore) { maxScore = currentScore; } } return maxScore; }};int main() { vector<int> cardPoints = {1, 2, 3, 4, 5, 6, 1}; int k = 3; Solution solution; cout << solution.maxScore(cardPoints, k) << endl; return 0;}Complexity Analysis
Time Complexity: O(K), because the first K cards require one summation and K replacements generate all remaining splits.
Space Complexity: O(1), because only score variables and index variables require auxiliary storage.
FAQS
Q1. Why does every valid selection correspond to a left-right split?
Card removal is allowed only from either end. After exactly K removals, selected cards must contain a prefix, a suffix, or a combination of both.
Q2. Why does the Brute Force Approach require O(K²) time?
Every one of the K + 1 splits recalculates up to K selected values.
Q3. Why do prefix and suffix sums improve the Better Approach?
Precomputed sums provide every split score through two array accesses and one addition.
Q4. Why does the Optimal Approach begin with the first K cards?
Starting with the first K cards is only one convenient choice. We can also begin with the last K cards and gradually replace cards from the right selection with cards from the beginning. Both directions generate all K + 1 possible left-right splits and give the same O(K) time and O(1) auxiliary space.
Q5. Why does every replacement preserve exactly K selected cards?
One selected left card leaves the score while one right card enters the score during the same step.
Q6. Does the method support negative card values?
Yes. Initializing maxScore from a valid selection prevents an incorrect default score of 0 when every valid score is negative.
Be the first to add a comment.