1005. Maximize Sum Of Array After K Negations


Problem Description

When given an integer array nums and an integer k, we're tasked to carry out a specific modification process to the array a total of k times. This modification involves choosing an index i and then flipping the sign of the number at that index (turning a positive number into a negative one or vice versa). The goal is to maximize the sum of elements in the array after making exactly k sign changes.

Intuition

The thinking process behind the solution is to first increase the sum of the array. We do this by flipping the sign of negative numbers because converting negatives to positives contributes to an increase in the total sum. The priority is to flip the smallest negative numbers (largest absolute value), as this will have the most significant impact on the sum.

The solution approach involves:

  1. Using a Counter from the collections module to count the occurrences of each number in the array. This is a more efficient way to handle duplicates and manage the operations rather than sorting or manually manipulating the array.

  2. We first consider all negative numbers, starting with the smallest (closest to -100, since the problem limits numbers to the range [-100, 100]). If a negative number exists, we flip it to positive and decrease our k accordingly. This is only beneficial if k remains non-zero after the operation.

  3. If we still have k operations left after dealing with all negative numbers, we now look at k's parity (whether it is even or odd). If k is even, we can ignore the rest of the operations since flipping any number twice will just return it to its original state (a no-op in terms of the array sum). If k is odd, we need to perform one more flip to maximize the sum.

  4. In the case where k is odd, we flip the smallest positive number (including zero, if present). We do this because, after all negatives have been flipped (if k allowed), this will have the smallest impact on decreasing the sum (since we have to perform an odd number of flips).

  5. After modifying the array according to the above rules, we calculate and return the sum of the final numbers, which represents the largest sum we can achieve.

The usage of Counter and flipping based on the smallest absolute values allows us to perform the minimum number of operations to achieve the highest sum.

Learn more about Greedy and Sorting patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

What is the running time of the following code?

1int sqrt(int n) {
2  for (int guess = 1; guess * guess <= n; guess++) {
3    if (guess * guess == n) {
4      return guess;
5    }
6  }
7  return -1;
8}

Solution Approach

The given Python solution to maximize the sum of the integer array after k negations makes use of several programming concepts, including:

  • A counter: A Counter from the collections module is utilized to maintain a count of each distinct element present in the array.

  • Looping through a range of numbers: A loop is employed to iterate through a range of numbers from -100 to -1 (inclusive), corresponding to potential negative numbers in nums.

  • Conditional checks and updates: Inside this loop, the algorithm checks for the presence of a negative number x in nums (determined by if cnt[x]) and calculates how many times this number should be flipped using min(cnt[x], k). If a flip is possible, the counter for x is decreased, while the counter for -x (the positive counterpart) is increased. The number of remaining flips k is decremented by the number of flips made, and if k drops to zero, the loop breaks as no more flips are permitted.

  • Handling the parity of the number of flips k: After negating as many negative numbers as possible, if there is an odd number of flips remaining (k & 1), and there are no zeros in the array to negate (since negating zero does not affect the sum), the algorithm looks for the smallest positive number (in the range of 1 to 100) to negate.

  • Summation of the array: Ultimately, the sum of the elements in the array is calculated using list comprehension and the items in the counter. The product of x * v for each number x and its count v is summed up to obtain the final result.

The algorithm is efficient as it prioritizes flipping the most significant negative numbers, handles the parity of k wisely, and performs a minimal number of operations by skipping unnecessary flips when k is even. Additionally, by employing a Counter, the solution avoids redundant re-computation by smartly tracking and updating the count of each number after each operation.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:

Example Walkthrough

Let's walk through a small example to illustrate the solution approach. Suppose we have the array nums = [3, -4, -2, 5] and k = 3. Our goal is to maximize the sum of the array after making exactly k flips.

We start by using a Counter to count occurrences:

1Counter({3: 1, -4: 1, -2: 1, 5: 1})

Step by step, we perform the following operations:

  1. Flip the smallest negative number. The smallest (in terms of value) negative number is -4. So, we flip it to 4. Now, nums = [3, 4, -2, 5], k = 2, and the Counter updates to {3: 1, -4: 0, 4: 1, -2: 1, 5: 1}.

  2. We still have flips left (k > 0), so we flip the next smallest negative number -2 to 2. Now nums = [3, 4, 2, 5], k = 1, and the Counter becomes {3: 1, -2: 0, 2: 1, 4: 1, 5: 1}.

  3. With k now equal to 1 (which is odd), we need to perform one more flip. We look for the smallest positive number, which is 2, and flip it back to -2 (if there were a zero, we would flip that instead as it would not affect the sum). Now nums = [3, 4, -2, 5] and k = 0. The Counter updates to {3: 1, 2: 0, -2: 1, 4: 1, 5: 1}.

  4. No more flips remain; k = 0. We calculate the sum of the array using the Counter. The array now looks like [3, 4, -2, 5], yielding a sum of 10.

So the maximum sum we can achieve with k = 3 flips for the array [3, -4, -2, 5] is 10. The solution approach effectively prioritizes the flips to maximize the sum by first flipping the smallest negatives and then handling the case where an odd number of flips is left by flipping the smallest positive number.

Solution Implementation

1from collections import Counter
2from typing import List
3
4class Solution:
5    def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:
6        # Count the occurrences of each number in the array.
7        num_counter = Counter(nums)
8      
9        # Iterate over negative numbers since negating negatives can increase total sum.
10        for x in range(-100, 0):
11            if num_counter[x]:
12                # Determine the minimum between the count of the current number and k.
13                num_negations = min(num_counter[x], k)
14                num_counter[x] -= num_negations  # Decrease the count for the current number.
15                num_counter[-x] += num_negations  # Increase the count for the opposite positive number.
16                k -= num_negations  # Decrease k by the number of negations performed.
17                if k == 0:  # Break if all negations have been used up.
18                    break
19       
20        # If there are an odd number of negations left and no zero in the list,
21        # it's optimal to flip the smallest positive number (if it exists).
22        if k % 2 == 1 and num_counter[0] == 0:
23            for x in range(1, 101):  # Look for the smallest positive number.
24                if num_counter[x]:
25                    num_counter[x] -= 1  # Decrease its count.
26                    num_counter[-x] += 1  # Increase the count for its negative.
27                    break
28
29        # Calculate the final sum.
30        return sum(x * occurrence for x, occurrence in num_counter.items())
31
1class Solution {
2
3    public int largestSumAfterKNegations(int[] nums, int k) {
4        // Create a frequency map to store the occurrence of each number
5        Map<Integer, Integer> frequency = new HashMap<>();
6        for (int num : nums) {
7            frequency.merge(num, 1, Integer::sum);
8        }
9
10        // Negate K numbers starting with the smallest (most negative) number
11        for (int i = -100; i < 0 && k > 0; ++i) {
12            if (frequency.getOrDefault(i, 0) > 0) {
13                // Determine the number of negations allowed for the number i
14                int negations = Math.min(frequency.get(i), k);
15                // Decrease the count for this number after negations
16                frequency.merge(i, -negations, Integer::sum);
17                // Increase the count for its positive pair after negations
18                frequency.merge(-i, negations, Integer::sum);
19                // Decrease the number of remaining negations
20                k -= negations;
21            }
22        }
23
24        // If there is an odd number of negations left and 0 is not present,
25        // we must negate the smallest positive number
26        if ((k % 2 == 1) && frequency.getOrDefault(0, 0) == 0) {
27            for (int i = 1; i <= 100; ++i) {
28                if (frequency.getOrDefault(i, 0) > 0) {
29                    // Negate one occurrence of the smallest positive number
30                    frequency.merge(i, -1, Integer::sum);
31                    // Add one occurrence of its negation
32                    frequency.merge(-i, 1, Integer::sum);
33                    break;
34                }
35            }
36        }
37
38        // Calculate the sum of all numbers after the negations
39        int sum = 0;
40        for (Map.Entry<Integer, Integer> entry : frequency.entrySet()) {
41            sum += entry.getKey() * entry.getValue();
42        }
43
44        return sum;
45    }
46}
47
1class Solution {
2public:
3    int largestSumAfterKNegations(vector<int>& nums, int k) {
4        unordered_map<int, int> countMap; // Map to store the frequency of each number
5      
6        // Count the frequency of each number in the array
7        for (int number : nums) {
8            ++countMap[number];
9        }
10      
11        // Negate the negative numbers if possible, starting from the smallest
12        for (int x = -100; x < 0 && k > 0; ++x) {
13            if (countMap[x]) { // If there are occurrences of 'x'
14                int times = min(countMap[x], k); // Find the min between count and remaining k
15                countMap[x] -= times; // Decrease the count for 'x'
16                countMap[-x] += times; // Increase the count for '-x', effectively negating 'x'
17                k -= times; // Decrease k by the number of negations performed
18            }
19        }
20      
21        // If there are remaining negations 'k' and there's no zero in the array
22        if (k % 2 == 1 && !countMap[0]) {
23            // Find the smallest positive number to negate
24            for (int x = 1; x <= 100; ++x) {
25                if (countMap[x]) {
26                    --countMap[x]; // Decrement the count of this number
27                    ++countMap[-x]; // Increment the count of its negation
28                    break; // Only negate once, then break
29                }
30            }
31        }
32      
33        // Calculate the final sum after the possible negations
34        int sum = 0;
35        for (const auto& [value, frequency] : countMap) {
36            sum += value * frequency; // Sum = number * its frequency
37        }
38      
39        return sum; // Return the final sum
40    }
41};
42
1// This function calculates the largest sum we can achieve by negating K elements
2// in the given array 'nums'.
3function largestSumAfterKNegations(nums: number[], k: number): number {
4    // Create a frequency map to count occurrences of each number
5    const frequency: Map<number, number> = new Map();
6  
7    // Fill the frequency map with the numbers from 'nums'
8    for (const number of nums) {
9        frequency.set(number, (frequency.get(number) || 0) + 1);
10    }
11  
12    // Process the numbers from -100 to -1
13    for (let number = -100; number < 0 && k > 0; ++number) {
14        // If the current number has a frequency greater than 0 and we still have k negations
15        if (frequency.get(number)! > 0) {
16            // Determine how many negations we can perform (limited by k and the frequency)
17            const negations = Math.min(frequency.get(number) || 0, k);
18          
19            // Decrease the frequency of the current number by the negations performed
20            frequency.set(number, (frequency.get(number) || 0) - negations);
21          
22            // Increase the frequency of the number's positive counterpart
23            frequency.set(-number, (frequency.get(-number) || 0) + negations);
24          
25            // Decrease the count of remaining negations
26            k -= negations;
27        }
28    }
29  
30    // Check for remaining negations; if we have an odd number and no zero is present in 'nums'
31    if ((k & 1) === 1 && (frequency.get(0) || 0) === 0) {
32        // Apply the negation to the smallest positive number
33        for (let number = 1; number <= 100; ++number) {
34            if (frequency.get(number)! > 0) {
35                // Decrease the frequency of the smallest positive number by 1
36                frequency.set(number, (frequency.get(number) || 0) - 1);
37              
38                // Increase the frequency of the number's negative counterpart by 1
39                frequency.set(-number, (frequency.get(-number) || 0) + 1);
40                break;
41            }
42        }
43    }
44  
45    // Calculate the sum of all numbers, taking their frequencies into account
46    let totalSum = 0;
47    for (const [num, freq] of frequency.entries()) {
48        totalSum += num * freq;
49    }
50  
51    // Return the computed total sum
52    return totalSum;
53}
54
Not Sure What to Study? Take the 2-min Quiz:

Which of these pictures shows the visit order of a depth-first search?

Time and Space Complexity

Time Complexity

The given code has a time complexity of O(N + K) where N is the size of the input list nums.

Here's the breakdown:

  • Counting the elements into the Counter object cnt takes O(N) time.
  • The first for loop runs for at most 100 iterations (from -100 to -1), which is a constant, hence O(1). However, within this loop, operations take place min(cnt[x], k) times, which could approach k. Therefore, in the worst case where all elements are negative and k is large, it could be O(K).
  • The check for odd k when there's no zero in the array, and then the subsequent for loop to find the smallest positive number, runs in at most 100 iterations again. Therefore, it consumes constant O(1) time.
  • Finally, summing up the elements in the cnt takes O(N) as it has to iterate through all elements once.

Therefore, we combine these to find O(N + K + 1 + 1), which simplifies to O(N + K).

Space Complexity

The space complexity of the code is O(N) because:

  • The Counter object cnt contains at most N unique integers, which is equal to the size of the input list nums.
  • No other data structures are used that scale with the size of the input.

Thus, the code requires additional space proportional to the number of unique elements in nums, which at maximum could be all the elements in nums, so O(N).

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

Given an array of 1,000,000 integers that is almost sorted, except for 2 pairs of integers. Which algorithm is fastest for sorting the array?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫