Maximum Number of Ways to Partition an Array
HardPrompt
You are given a 0-indexed integer array nums of length n. The number of ways to partition nums is the number of pivot indices that satisfy both conditions:
1 <= pivot < nnums[0] + nums[1] + ... + nums[pivot - 1] == nums[pivot] + nums[pivot + 1] + ... + nums[n - 1]
You are also given an integer k. You can choose to change the value of one element of nums to k, or to leave the array unchanged.
Return the maximum possible number of ways to partition nums to satisfy both conditions after changing at most one element.
Example 1:
Input: nums = [2,-1,2], k = 3
Output: 1
Explanation: One optimal approach is to change nums[0] to k. The array becomes [3,-1,2].
There is one way to partition the array:
- For pivot = 2, we have the partition [3,-1 | 2]: 3 + -1 == 2.Example 2:
Input: nums = [0,0,0], k = 1
Output: 2
Explanation: The optimal approach is to leave the array unchanged.
There are two ways to partition the array:
- For pivot = 1, we have the partition [0 | 0,0]: 0 == 0 + 0.
- For pivot = 2, we have the partition [0,0 | 0]: 0 + 0 == 0.Example 3:
Input: nums = [22,4,-25,-20,-15,15,-16,7,19,-10,0,-13,-14], k = -33
Output: 4
Explanation: One optimal approach is to change nums[2] to k. The array becomes [22,4,-33,-20,-15,15,-16,7,19,-10,0,-13,-14].
There are four ways to partition the array.
Constraints:
n == nums.length2 <= n <= 105-105 <= k, nums[i] <= 105
Approaches
2 approaches with complexity analysis and trade-offs.
This approach simulates the process directly. It first calculates the number of partitions for the original, unchanged array. Then, it iterates through every possible index i from 0 to n-1, hypothetically changing nums[i] to k. For each change, it recalculates the total sum and all prefix sums, and then counts the number of valid partitions. The maximum count found across all these scenarios (including the no-change case) is the answer.
Algorithm
- Define a helper function
countPartitions(array)that takes an array, calculates its total sum and prefix sums, and returns the number of valid partitions. This takes O(n) time. - Calculate the initial number of partitions for the unchanged
numsarray using the helper function. Store this asmax_partitions. - Iterate through each index
ifrom0ton-1. - In each iteration, create a temporary copy of
nums, change the element at indexitok. - Call
countPartitionson the temporary array. - Update
max_partitions = max(max_partitions, new_count). - Return
max_partitions.
Walkthrough
Start by calculating the number of ways to partition the original array. This serves as our initial maximum.
The condition for a valid partition at pivot p is sum(nums[0...p-1]) == sum(nums[p...n-1]). This can be checked efficiently using prefix sums. Let S be the total sum, and prefix[p] be the sum of the first p elements. The condition is prefix[p] == S - prefix[p], or 2 * prefix[p] == S.
We can pre-calculate all prefix sums of the original array in O(n) time.
Then, loop through each index i from 0 to n-1. For each i, consider changing nums[i] to k.
Inside this loop, create a temporary modified array.
Recalculate the prefix sums and the total sum for this modified array.
Count the number of valid partitions for the modified array by iterating through all possible pivots p from 1 to n-1 and checking the partition condition.
Keep track of the maximum number of partitions found so far.
After checking all possible single-element changes, the maximum value recorded is the result.
class Solution { private int countPartitions(int[] arr) { int n = arr.length; long totalSum = 0; for (int x : arr) { totalSum += x; } if (totalSum % 2 != 0 && n > 0) { // Optimization: if total sum is odd, no integer prefix sum can be half of it. // But wait, prefix sums can be non-integers if array has non-integers. Here they are integers. // The logic `currentPrefixSum * 2 == totalSum` handles this correctly anyway. } long currentPrefixSum = 0; int count = 0; // Pivot p is between index p-1 and p. 1 <= p < n. for (int p = 1; p < n; p++) { currentPrefixSum += arr[p - 1]; if (currentPrefixSum * 2 == totalSum) { count++; } } return count; } public int maxWaysToPartition(int[] nums, int k) { int n = nums.length; // Case 1: No change int maxPartitions = countPartitions(nums); // Case 2: Change one element for (int i = 0; i < n; i++) { int originalValue = nums[i]; nums[i] = k; // Modify the array maxPartitions = Math.max(maxPartitions, countPartitions(nums)); nums[i] = originalValue; // Backtrack } return maxPartitions; }}Complexity
Time
O(n^2), where `n` is the length of the array. The outer loop runs `n+1` times (for each element to change, plus the no-change case), and inside, counting partitions takes O(n) time.
Space
O(n) if a copy of the array is made in each iteration. If the array is modified in-place and then restored, the space complexity is O(1) besides the input storage.
Trade-offs
Pros
Simple to understand and implement.
Directly follows the problem statement.
Cons
Inefficient due to repeated calculations. For each of the
npossible changes, it performs O(n) work, leading to a quadratic time complexity which is too slow for the given constraints.
This approach avoids the O(n^2) complexity by precomputing information and using hash maps to perform lookups in constant time on average. Instead of re-calculating for each change, we analyze how changing a single element nums[i] affects the partition condition. The key insight is that a change at index i has a different effect on pivots before i versus pivots after i. We can iterate through all possible change locations i and, for each, quickly query the number of pivots that would become valid.
Algorithm
- Calculate the prefix sums of the
numsarray. Let the array beprefix. - Calculate the number of partitions with no changes (
noChangeAns) and initializemaxPartitions = noChangeAns. - Initialize a hash map
rightCountswith the frequencies ofprefix[p]for all possible pivotsp(1 <= p < n). - Initialize an empty hash map
leftCounts. - Iterate with index
ifrom0ton-1(the index of the element to be changed). - Inside the loop, calculate
diff = k - nums[i]. - Calculate the number of new partitions:
- Count pivots
p <= isatisfying2 * prefix[p] == totalSum + diffby looking up(totalSum + diff) / 2inleftCounts. - Count pivots
p > isatisfying2 * prefix[p] == totalSum - diffby looking up(totalSum - diff) / 2inrightCounts.
- Count pivots
- Update
maxPartitionswith the sum of these two counts if it's greater. - Prepare for the next iteration by moving the count for
prefix[i+1]fromrightCountstoleftCounts. - Return
maxPartitions.
Walkthrough
Let S be the total sum of the original array and prefix[p] be the sum of elements nums[0...p-1]. A partition at pivot p is valid if 2 * prefix[p] == S.
If we change nums[i] to k, the change in value is diff = k - nums[i]. The new total sum is S' = S + diff.
The partition condition 2 * new_prefix[p] == S' is affected differently based on p's relation to i:
- For a pivot
p <= i, the prefix sumprefix[p]is unchanged. The condition becomes2 * prefix[p] == S + diff. - For a pivot
p > i, the prefix sumprefix[p]increases bydiff. The condition becomes2 * (prefix[p] + diff) == S + diff, which simplifies to2 * prefix[p] == S - diff. We can solve this by iterating through each possible indexito change, and for eachi, counting how many pivots satisfy these two new conditions. To do this counting efficiently, we use two hash maps:leftCountsandrightCounts. leftCountswill store the frequencies ofprefix[p]values for pivotsp <= i.rightCountswill store the frequencies ofprefix[p]values for pivotsp > i. We first calculate all prefix sums. Then, we initializerightCountswith frequencies of allprefix[p]forpfrom1ton-1.leftCountsis initially empty. We then iterateifrom0ton-1. In each step, we use the maps to find the number of valid partitions for changingnums[i]. After that, we update the maps by moving the counts for pivotp = i+1fromrightCountstoleftCountsto prepare for the next iteration. The base case (no change) is also calculated and used to initialize the maximum.
import java.util.HashMap;import java.util.Map; class Solution { public int maxWaysToPartition(int[] nums, int k) { int n = nums.length; long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } long totalSum = prefix[n]; // Case 1: No change int noChangeAns = 0; for (int p = 1; p < n; p++) { if (prefix[p] * 2 == totalSum) { noChangeAns++; } } int maxPartitions = noChangeAns; // Case 2: Change one element Map<Long, Integer> leftCounts = new HashMap<>(); Map<Long, Integer> rightCounts = new HashMap<>(); for (int p = 1; p < n; p++) { rightCounts.put(prefix[p], rightCounts.getOrDefault(prefix[p], 0) + 1); } for (int i = 0; i < n; i++) { long diff = k - nums[i]; int currentPartitions = 0; // Check pivots p <= i long targetLeftSum = totalSum + diff; if (targetLeftSum % 2 == 0) { currentPartitions += leftCounts.getOrDefault(targetLeftSum / 2, 0); } // Check pivots p > i long targetRightSum = totalSum - diff; if (targetRightSum % 2 == 0) { currentPartitions += rightCounts.getOrDefault(targetRightSum / 2, 0); } maxPartitions = Math.max(maxPartitions, currentPartitions); // Update maps for the next iteration (i+1) // Pivot p = i + 1 moves from right to left if (i + 1 < n) { long p_val = prefix[i + 1]; rightCounts.put(p_val, rightCounts.getOrDefault(p_val, 0) - 1); if (rightCounts.get(p_val) == 0) { rightCounts.remove(p_val); } leftCounts.put(p_val, leftCounts.getOrDefault(p_val, 0) + 1); } } return maxPartitions; }}Complexity
Time
O(n). Calculating prefix sums takes O(n). Populating the initial hash map takes O(n). The main loop runs `n` times, and each operation inside (map lookups and updates) takes O(1) on average.
Space
O(n). We need O(n) space for the prefix sum array and O(n) for the hash maps in the worst case where all prefix sums are distinct.
Trade-offs
Pros
Highly efficient with linear time complexity.
Avoids redundant computations by using hash maps for quick lookups.
Cons
More complex to reason about and implement correctly compared to the brute-force approach.
Requires careful handling of indices and map updates.
Solutions
Solution
class Solution {public int waysToPartition(int[] nums, int k) { int n = nums.length; int[] s = new int[n]; s[0] = nums[0]; Map<Integer, Integer> right = new HashMap<>(); for (int i = 0; i < n - 1; ++i) { right.merge(s[i], 1, Integer : : sum); s[i + 1] = s[i] + nums[i + 1]; } int ans = 0; if (s[n - 1] % 2 == 0) { ans = right.getOrDefault(s[n - 1] / 2, 0); } Map<Integer, Integer> left = new HashMap<>(); for (int i = 0; i < n; ++i) { int d = k - nums[i]; if ((s[n - 1] + d) % 2 == 0) { int t = left.getOrDefault((s[n - 1] + d) / 2, 0) + right.getOrDefault((s[n - 1] - d) / 2, 0); ans = Math.max(ans, t); } left.merge(s[i], 1, Integer : : sum); right.merge(s[i], -1, Integer : : sum); } return ans; }}class Solution {public: int waysToPartition(vector<int> &nums, int k) { int n = nums.size(); long long s[n]; s[0] = nums[0]; unordered_map<long long, int> right; for (int i = 0; i < n - 1; ++i) { right[s[i]]++; s[i + 1] = s[i] + nums[i + 1]; } int ans = 0; if (s[n - 1] % 2 == 0) { ans = right[s[n - 1] / 2]; } unordered_map<long long, int> left; for (int i = 0; i < n; ++i) { int d = k - nums[i]; if ((s[n - 1] + d) % 2 == 0) { int t = left[(s[n - 1] + d) / 2] + right[(s[n - 1] - d) / 2]; ans = max(ans, t); } left[s[i]]++; right[s[i]]--; } return ans; }};class Solution: def waysToPartition(self, nums: List[int], k: int) -> int: n = len(nums) s = [nums[0]] * n right = defaultdict(int) for i in range(1, n): s[i] = s[i - 1] + nums[i] right[s[i - 1]] += 1 ans = 0 if s[- 1] % 2 == 0: ans = right[s[- 1] // 2] left = defaultdict(int) for v, x in zip(s, nums): d = k - x if (s[- 1] + d) % 2 == 0: t = left[(s[- 1] + d) // 2] + right[(s[- 1] - d) // 2] if ans < t: ans = t left[v] += 1 right[v] -= 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.