Problem
Given an array of non-negative integers and an integer k, partition the array into exactly k contiguous, non-empty subarrays so that the largest subarray sum is as small as possible. Return that minimum possible largest sum.
- Input: nums = [7, 2, 5, 10, 8], k = 2
- Output: 18
- Explanation: Splitting as [7, 2, 5] and [10, 8] gives a maximum subarray sum of 18, which is better than any other 2-way split.
Intuition
The answer must be at least max(nums) โ one subarray always contains the largest element โ and at most sum(nums) โ the case where k = 1. This bounded range makes the problem amenable to binary search on the answer itself: for any candidate limit, a single greedy pass determines whether the array can be partitioned into k subarrays that each stay within that limit.
Approach 1 โ Dynamic Programming
Build a table where dp[i][j] is the minimum possible largest sum when splitting the first i elements into j groups. For each state, try every position m as the start of the last group and take the minimum.
- Precompute prefix sums to evaluate any subarray sum in O(1).
- Initialize
dp[0][0] = 0; all other entries start at infinity. - For each prefix length
iand group countj, iterate over all split pointsm(last group isnums[m..i-1]). - Update
dp[i][j] = min(dp[i][j], max(dp[m][j-1], prefix_sum[i] - prefix_sum[m])). - Return
dp[n][k].
1def splitArray(nums: list[int], k: int) -> int:
2 n = len(nums)
3 prefix_sum = [0] * (n + 1)
4 for i in range(n):
5 prefix_sum[i + 1] = prefix_sum[i] + nums[i]
6
7 INF = float('inf')
8 dp = [[INF] * (k + 1) for _ in range(n + 1)]
9 dp[0][0] = 0
10
11 for i in range(1, n + 1):
12 for j in range(1, min(i, k) + 1):
13 for m in range(j - 1, i): # last group spans nums[m..i-1]
14 last_group_sum = prefix_sum[i] - prefix_sum[m]
15 candidate = max(dp[m][j - 1], last_group_sum)
16 if candidate < dp[i][j]:
17 dp[i][j] = candidate
18
19 return dp[n][k]Time: O(nยฒ ร k) โ three nested loops over prefix length, group count, and split point.
Space: O(n ร k) for the dp table; O(n) additional for prefix sums.
Approach 2 โ Binary Search on Answer
Binary search between max(nums) (the minimum possible answer) and sum(nums) (the maximum). For each candidate limit, greedily assign elements to the current subarray; whenever adding the next element would exceed the limit, open a new subarray. If the total subarray count stays within k, the limit is achievable.
- Set
lo = max(nums),hi = sum(nums). - While
lo < hi, computemid = (lo + hi) // 2. - Run a greedy pass: accumulate elements into a group; when adding
numwould exceedmid, increment group count and start fresh withnum. - If group count โค k, the limit
midworks โ shrink the window by settinghi = mid. - Otherwise, the limit is too tight โ raise the floor with
lo = mid + 1. - Return
lo.
1def splitArray(nums: list[int], k: int) -> int:
2 def can_fit(limit: int) -> bool:
3 group_count = 1
4 current_sum = 0
5 for num in nums:
6 if current_sum + num > limit:
7 group_count += 1 # start a new subarray
8 current_sum = num
9 if group_count > k:
10 return False
11 else:
12 current_sum += num
13 return True
14
15 lo, hi = max(nums), sum(nums)
16 while lo < hi:
17 mid = (lo + hi) // 2
18 if can_fit(mid):
19 hi = mid # valid limit; try to tighten it
20 else:
21 lo = mid + 1 # limit too small; raise the floor
22 return loTime: O(n ร log(sum(nums))) โ log iterations of binary search, each requiring an O(n) greedy pass.
Space: O(1) โ only a handful of counters and accumulators beyond the input.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Dynamic Programming | O(nยฒ ร k) | O(n ร k) | When n and k are both small and you need a table-based recurrence for debugging or extension |
| Binary Search on Answer | O(n log(sum)) | O(1) | Almost always preferred โ dramatically faster and uses constant extra memory |
Common Mistakes
- Setting
lo = 0instead oflo = max(nums)โ if the limit is less than the largest element, the greedy check will immediately try to open a second group for that element alone, inflating the count; the boundmax(nums)makes every individual element valid. - Using
hi = mid - 1in the binary search update โ this skips the case wheremidis exactly the answer; always usehi = midwhen searching for the minimum valid value andlo < hias the loop condition. - Starting
group_countat 0 โ the array is never empty; there is always at least one group in progress, so initialize to 1. - Initializing
loandhiwith regularintin Java/C++ โsum(nums)can exceedInteger.MAX_VALUEfor large inputs; uselongthroughout or you risk silent overflow in the midpoint calculation. - Forgetting that the greedy pass assigns each element to exactly one group โ a common variant bug is skipping the current element when starting a new group, which would leave it unassigned; the new group starts with
current_sum = num, notcurrent_sum = 0.
Related Problems
capacity-to-ship-packages-within-d-daysโ structurally identical: binary search on the minimum capacity that fits all packages in d dayskoko-eating-bananasโ same "binary search on answer, greedy feasibility check" patternmagnetic-force-between-two-ballsโ binary search on the answer, but maximizing the minimum distance instead of minimizing the maximum summinimum-size-subarray-sumโ related subarray-sum reasoning, solved with a sliding window insteadpartition-equal-subset-sumโ partitioning an array into groups with a target constraint, using 0/1 knapsack DP instead of greedy binary search