1005. Maximize Sum Of Array After K Negations
Problem Description
You are given an integer array nums
and an integer k
. You need to modify the array by performing the following operation exactly k
times:
- Choose any index
i
in the array and replacenums[i]
with-nums[i]
(negate the element).
You can choose the same index multiple times across the k
operations. Your goal is to find the largest possible sum of the array after performing exactly k
negations.
For example, if you have nums = [4, 2, 3]
and k = 1
, you could negate the element at index 1 to get [4, -2, 3]
with sum 5, or negate the element at index 0 to get [-4, 2, 3]
with sum 1, or negate the element at index 2 to get [4, 2, -3]
with sum 3. The maximum sum would be 5.
The key insight is that you want to maximize the final sum, so you should strategically choose which elements to negate. If there are negative numbers, it's beneficial to turn them positive. If you must perform more negations than there are negative numbers, you might need to repeatedly negate the same element (which effectively toggles its sign).
Intuition
To maximize the sum, we want as many positive numbers as possible with the largest absolute values. Let's think about the optimal strategy:
-
Prioritize negating negative numbers: If we have negative numbers, we should negate them first to turn them positive, which increases our sum. Among negative numbers, we should start with the smallest (most negative) ones since negating
-5
gives us+5
, which adds more to our sum than negating-2
to get+2
. -
What if
k
is larger than the number of negative elements?: After converting all negative numbers to positive, we still have operations left. Here, we face a choice - we need to keep negating something. The smart move is to repeatedly negate the smallest absolute value in the array. If we negate it an even number of times, it returns to its original sign. If odd, it changes sign. -
The parity of remaining operations matters: After handling all negative numbers, if we have an even number of operations left, we can just toggle the same number back and forth, leaving everything positive. But if we have an odd number left, we're forced to have one negative number in our final array - so we choose the smallest absolute value to minimize the loss.
-
Using counting to optimize: Since the problem constrains values to
[-100, 100]
, we can use a counting approach. We count occurrences of each value, then process them in order from most negative to least negative. For each negative valuex
, we can negate up tomin(count[x], k)
occurrences, effectively moving them from count ofx
to count of-x
. -
Handling the final odd operation: If after processing all negatives we still have an odd number of operations left (checked by
k & 1
), and there's no zero in the array to absorb the negation, we must negate the smallest positive number once.
Solution Approach
The solution uses a greedy approach with counting to efficiently handle the negations:
-
Initialize a Counter: We use
Counter(nums)
to create a hash tablecnt
that stores the frequency of each element in the array. This allows us to track how many times each value appears. -
Process negative numbers from smallest to largest: We iterate through the range
[-100, 0)
to handle negative numbers in ascending order (most negative first). For each negative valuex
:- Check if
cnt[x]
exists (i.e., this negative value is present in our array) - Calculate
m = min(cnt[x], k)
- this is the number of times we'll negate this value (limited by either how many we have or how many operations remain) - Update the counts: decrease
cnt[x]
bym
and increasecnt[-x]
bym
(we're convertingm
occurrences ofx
to-x
) - Decrease
k
bym
to track remaining operations - If
k
becomes 0, we've used all operations and can stop
- Check if
-
Handle remaining odd operations: After processing all negative numbers, if
k
is still odd (k & 1
checks if the least significant bit is 1) and there's no zero in the array (cnt[0] == 0
):- We need to negate one more element, and it should be the smallest positive number to minimize loss
- Iterate from
1
to100
to find the smallest positive number - When found, decrease its count by 1 and increase the count of its negation by 1
- This effectively flips one occurrence of the smallest positive number to negative
-
Calculate the final sum: Use
sum(x * v for x, v in cnt.items())
to compute the total. For each unique valuex
with countv
, we addx * v
to the sum.
The key insight is that by using counting, we avoid repeatedly modifying the actual array. Instead, we track how many of each value we have, making bulk transfers from negative to positive counts. This approach has a time complexity of O(n + C)
where C
is the range of values (201 in this case), which is more efficient than repeatedly sorting or scanning the array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [3, -1, 0, 2]
and k = 3
.
Step 1: Initialize Counter
- Create a frequency counter:
cnt = {3: 1, -1: 1, 0: 1, 2: 1}
- We have one occurrence each of 3, -1, 0, and 2
Step 2: Process negative numbers from smallest to largest
- Start iterating from -100 to -1
- At
x = -1
: We findcnt[-1] = 1
(we have one -1)- Calculate
m = min(1, 3) = 1
(we can negate this one occurrence) - Update counts:
cnt[-1] = 0
andcnt[1] = 1
(convert -1 to +1) - Update
k = 3 - 1 = 2
- Calculate
- Continue iterating but no more negative numbers found
- After this step:
cnt = {3: 1, -1: 0, 0: 1, 2: 1, 1: 1}
andk = 2
Step 3: Handle remaining operations
- We still have
k = 2
operations left - Check if
k
is odd:2 & 1 = 0
(even), so no action needed - Since k is even, we can toggle the same element back and forth (e.g., 0 โ 0 โ 0), leaving everything as is
Step 4: Calculate final sum
- Sum =
3*1 + (-1)*0 + 0*1 + 2*1 + 1*1 = 3 + 0 + 0 + 2 + 1 = 6
The final array effectively becomes [3, 1, 0, 2]
with sum 6.
Alternative scenario with odd remaining operations:
If we had nums = [2, -3, 5]
and k = 2
:
Step 1: cnt = {2: 1, -3: 1, 5: 1}
Step 2: Process negatives
- At
x = -3
: negate it,cnt = {2: 1, -3: 0, 5: 1, 3: 1}
,k = 1
Step 3: Handle remaining odd operation
k = 1
is odd andcnt[0] = 0
(no zero to absorb the negation)- Find smallest positive: iterate from 1 upward
- At
x = 2
: foundcnt[2] = 1
- Negate one occurrence:
cnt[2] = 0
,cnt[-2] = 1
Step 4: Calculate sum
- Sum =
(-2)*1 + 3*1 + 5*1 = -2 + 3 + 5 = 6
The array effectively becomes [-2, 3, 5]
with sum 6.
Solution Implementation
1class Solution:
2 def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
3 # Count frequency of each number in the array
4 frequency_map = Counter(nums)
5
6 # Process negative numbers from smallest to largest (-100 to -1)
7 # Converting negatives to positives maximizes the sum
8 for value in range(-100, 0):
9 if frequency_map[value]:
10 # Calculate how many times we can negate this value
11 # Limited by either its frequency or remaining k
12 negations_to_apply = min(frequency_map[value], k)
13
14 # Update frequencies: reduce count of negative, increase count of positive
15 frequency_map[value] -= negations_to_apply
16 frequency_map[-value] += negations_to_apply
17
18 # Decrease remaining negations
19 k -= negations_to_apply
20
21 # If we've used all negations, stop processing
22 if k == 0:
23 break
24
25 # Handle remaining negations if k is odd and there's no zero in the array
26 # If k is odd after negating all negatives, we must negate one more number
27 # The optimal choice is the smallest positive number
28 if k & 1 and frequency_map[0] == 0:
29 # Find the smallest positive number to negate
30 for value in range(1, 101):
31 if frequency_map[value]:
32 # Negate one instance of the smallest positive number
33 frequency_map[value] -= 1
34 frequency_map[-value] += 1
35 break
36
37 # Calculate the final sum by multiplying each value by its frequency
38 return sum(value * frequency for value, frequency in frequency_map.items())
39
1class Solution {
2 public int largestSumAfterKNegations(int[] nums, int k) {
3 // Create a frequency map to count occurrences of each number
4 Map<Integer, Integer> frequencyMap = new HashMap<>();
5 for (int num : nums) {
6 frequencyMap.merge(num, 1, Integer::sum);
7 }
8
9 // Process negative numbers from smallest to largest (-100 to -1)
10 // Flip negative numbers to positive to maximize the sum
11 for (int num = -100; num < 0 && k > 0; num++) {
12 if (frequencyMap.getOrDefault(num, 0) > 0) {
13 // Calculate how many times we can flip this negative number
14 int flipsToPerform = Math.min(frequencyMap.get(num), k);
15
16 // Decrease count of negative number
17 frequencyMap.merge(num, -flipsToPerform, Integer::sum);
18 // Increase count of positive counterpart
19 frequencyMap.merge(-num, flipsToPerform, Integer::sum);
20
21 // Update remaining flips
22 k -= flipsToPerform;
23 }
24 }
25
26 // If k is odd and there's no zero in the array
27 // We need to flip the smallest positive number once
28 if ((k & 1) == 1 && frequencyMap.getOrDefault(0, 0) == 0) {
29 // Find the smallest positive number and flip it
30 for (int num = 1; num <= 100; num++) {
31 if (frequencyMap.getOrDefault(num, 0) > 0) {
32 // Decrease count of positive number by 1
33 frequencyMap.merge(num, -1, Integer::sum);
34 // Increase count of its negative counterpart by 1
35 frequencyMap.merge(-num, 1, Integer::sum);
36 break;
37 }
38 }
39 }
40
41 // Calculate the final sum
42 int totalSum = 0;
43 for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
44 totalSum += entry.getKey() * entry.getValue();
45 }
46
47 return totalSum;
48 }
49}
50
1class Solution {
2public:
3 int largestSumAfterKNegations(vector<int>& nums, int k) {
4 // Create a frequency map to count occurrences of each number
5 unordered_map<int, int> frequencyMap;
6 for (int& num : nums) {
7 ++frequencyMap[num];
8 }
9
10 // Process negative numbers from smallest to largest (-100 to -1)
11 // Convert negative numbers to positive to maximize the sum
12 for (int value = -100; value < 0 && k > 0; ++value) {
13 if (frequencyMap[value]) {
14 // Calculate how many negations we can perform on this value
15 int negationsToPerform = min(frequencyMap[value], k);
16
17 // Update frequency map: decrease count of negative value
18 frequencyMap[value] -= negationsToPerform;
19
20 // Increase count of corresponding positive value
21 frequencyMap[-value] += negationsToPerform;
22
23 // Decrease remaining negations
24 k -= negationsToPerform;
25 }
26 }
27
28 // Handle remaining odd number of negations
29 // If k is odd and there's no zero, we must negate the smallest positive number
30 if ((k & 1) && !frequencyMap[0]) {
31 // Find the smallest positive number to negate
32 for (int value = 1; value <= 100; ++value) {
33 if (frequencyMap[value]) {
34 // Negate one occurrence of the smallest positive number
35 --frequencyMap[value];
36 ++frequencyMap[-value];
37 break;
38 }
39 }
40 }
41
42 // Calculate the final sum by multiplying each value with its frequency
43 int totalSum = 0;
44 for (auto& [value, frequency] : frequencyMap) {
45 totalSum += value * frequency;
46 }
47
48 return totalSum;
49 }
50};
51
1/**
2 * Maximizes the sum of an array after applying K negations
3 * @param nums - The input array of integers
4 * @param k - The number of negation operations to perform
5 * @returns The maximum possible sum after K negations
6 */
7function largestSumAfterKNegations(nums: number[], k: number): number {
8 // Create a frequency map to count occurrences of each number
9 const frequencyMap: Map<number, number> = new Map();
10
11 // Populate the frequency map with counts of each number
12 for (const num of nums) {
13 frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
14 }
15
16 // Process negative numbers from -100 to -1
17 // Negate negative numbers to maximize sum (converting them to positive)
18 for (let currentNum = -100; currentNum < 0 && k > 0; currentNum++) {
19 const frequency = frequencyMap.get(currentNum) || 0;
20
21 if (frequency > 0) {
22 // Calculate how many negations we can perform on this number
23 const negationsToPerform = Math.min(frequency, k);
24
25 // Update frequency map: reduce count of negative number
26 frequencyMap.set(currentNum, frequency - negationsToPerform);
27
28 // Update frequency map: increase count of positive counterpart
29 frequencyMap.set(-currentNum, (frequencyMap.get(-currentNum) || 0) + negationsToPerform);
30
31 // Decrease remaining negations
32 k -= negationsToPerform;
33 }
34 }
35
36 // Handle remaining odd number of negations when no zeros exist
37 // If k is odd and there's no zero to absorb the negation,
38 // we need to negate the smallest positive number
39 if ((k & 1) === 1 && (frequencyMap.get(0) || 0) === 0) {
40 // Find the smallest positive number and negate it
41 for (let currentNum = 1; currentNum <= 100; currentNum++) {
42 const frequency = frequencyMap.get(currentNum) || 0;
43
44 if (frequency > 0) {
45 // Negate one occurrence of the smallest positive number
46 frequencyMap.set(currentNum, frequency - 1);
47 frequencyMap.set(-currentNum, (frequencyMap.get(-currentNum) || 0) + 1);
48 break;
49 }
50 }
51 }
52
53 // Calculate the final sum by multiplying each number by its frequency
54 let totalSum = 0;
55 for (const [value, count] of frequencyMap.entries()) {
56 totalSum += value * count;
57 }
58
59 return totalSum;
60}
61
Time and Space Complexity
The time complexity is O(n + M)
, where n
is the length of the array nums
and M
is the size of the data range (which is 201 in this case, from -100 to 100).
- Creating the Counter takes
O(n)
time to iterate through all elements innums
- The first loop iterates through the range from -100 to 0, which is
O(M)
operations - The second conditional block iterates through the range from 1 to 101 in the worst case, which is also
O(M)
operations - The final sum operation iterates through all items in the Counter, which is at most
O(M)
items - Overall:
O(n) + O(M) + O(M) + O(M) = O(n + M)
The space complexity is O(M)
, where M
is the size of the data range.
- The Counter
cnt
stores at mostM
unique values (all integers from -100 to 100), requiringO(M)
space - No other significant auxiliary space is used
- Overall:
O(M)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Remaining Even Operations Correctly
A common mistake is assuming that remaining operations after converting all negatives must be handled. When k
is even after processing all negative numbers, we don't need to do anything - we can repeatedly negate and un-negate the same element, leaving the array unchanged.
Incorrect approach:
# Wrong: Trying to handle both odd and even k
if k > 0: # This would incorrectly process even k values
for value in range(1, 101):
if frequency_map[value]:
frequency_map[value] -= 1
frequency_map[-value] += 1
break
Correct approach:
# Only handle when k is odd (k & 1 checks if k is odd) if k & 1 and frequency_map[0] == 0: # Find smallest positive to negate once
2. Forgetting to Check for Zero in the Array
When zeros exist in the array and we have remaining operations, we can repeatedly negate zero (which stays zero), effectively wasting the operations harmlessly. Failing to check for zeros might lead to unnecessarily negating a positive number.
Incorrect approach:
# Wrong: Not checking if zero exists
if k & 1: # Missing the zero check
for value in range(1, 101):
if frequency_map[value]:
frequency_map[value] -= 1
frequency_map[-value] += 1
break
Correct approach:
# Check both: k is odd AND no zeros exist if k & 1 and frequency_map[0] == 0: # Only then we need to negate a positive number
3. Processing Negative Numbers in Wrong Order
Processing larger negative numbers first (like -1, -2, -3) instead of smaller ones (-100, -99, -98) would lead to suboptimal results since converting more negative numbers yields larger gains.
Incorrect approach:
# Wrong: Processing from -1 to -100 (larger negatives first)
for value in range(-1, -101, -1):
if frequency_map[value]:
# This prioritizes -1 over -100, which is suboptimal
Correct approach:
# Process from most negative to least negative
for value in range(-100, 0): # -100, -99, ..., -2, -1
if frequency_map[value]:
# This ensures we convert the most negative numbers first
4. Modifying Dictionary While Iterating
While not directly an issue in this specific implementation, a variation where someone tries to iterate over frequency_map.keys()
or frequency_map.items()
while modifying it could cause runtime errors.
Potentially problematic approach:
# Could be problematic in some Python versions for value in frequency_map: if value < 0 and k > 0: frequency_map[value] -= 1 # Modifying during iteration frequency_map[-value] += 1
Safe approach:
# Iterate over a fixed range instead
for value in range(-100, 0):
if frequency_map[value]:
# Safe to modify frequency_map here
Which algorithm should you use to find a node that is close to the root of the tree?
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!