Topics › Binary Search › BS on Answers
Split array - largest sum
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array nums of non-negative integers and an integer k. Cut the array into exactly k non-empty, contiguous pieces. Every split has a “largest piece sum”, the biggest total among its k pieces. Among all possible splits, return the smallest possible largest piece sum. If k is larger than the number of elements, no split exists; return -1.
Constraints: 1 ≤ n ≤ 1000, 0 ≤ nums[i] ≤ 10^6, 1 ≤ k ≤ 50. The total of all elements fits in an int under these limits.
Example 1
nums = [7, 2, 5, 10, 8], k = 2
answer = 18 // [7, 2, 5] and [10, 8]: sums 14 and 18, the 18 cannot be lowered
Example 2
nums = [1, 2, 3, 4, 5], k = 2
answer = 9 // [1, 2, 3] and [4, 5]: sums 6 and 9
Example 3
nums = [1, 2, 3], k = 4
answer = -1 // four non-empty pieces cannot be cut from three elements
This is the same problem as the Book Allocation problem and the Painter’s Partition problem, just with different nouns. The whole family is solved by one idea: instead of searching for the split, search for the answer.
The check that every approach shares
Suppose someone hands us a limit L and asks: “can the array be cut into at most k pieces so that no piece sums to more than L?” That question is easy. Walk left to right and keep adding elements to the current piece; the moment the next element would push the piece over L, close the piece and start a new one. Being greedy here is safe: closing a piece later never helps, because a piece that is already as full as it can be leaves the most room for everything that follows.
static int piecesNeeded(int[] nums, int limit) {
int pieces = 1;
long sum = 0;
for (int x : nums) {
if (sum + x > limit) { pieces++; sum = 0; }
sum += x;
}
return pieces;
}
The function assumes limit is at least the largest element, so that a single element always fits in a piece on its own. Every caller below guarantees that. If piecesNeeded(nums, L) <= k, the limit L is achievable: we can always merge nothing and leave some pieces unused, or, equivalently, split a piece further, which never increases the largest sum.
Approach 1: Brute force over the answer
Intuition. The answer is a piece sum, so it lies between the largest single element (no piece can be smaller than its biggest member) and the total of the whole array (one piece holding everything). Try every candidate limit in that range in increasing order; the first one that needs at most k pieces is the smallest achievable limit, which is exactly what is asked.
Algorithm.
- If
k > n, return-1. - Compute
maxandsumof the array. - For
limit = max, max + 1, ..., sum: ifpiecesNeeded(nums, limit) <= k, returnlimit.
static int bruteForce(int[] nums, int k) {
int n = nums.length;
if (k > n) return -1;
int max = 0, sum = 0;
for (int x : nums) { max = Math.max(max, x); sum += x; }
for (int limit = max; limit <= sum; limit++) {
if (piecesNeeded(nums, limit) <= k) return limit;
}
return sum;
}
Complexity. Time O(n × (sum − max)): up to sum − max + 1 candidates, each checked in a linear pass. Space O(1).
With sum as large as 10^9 this is hopeless, but it establishes the two facts the optimal approach needs: the answer lives in [max, sum], and we can test any candidate in linear time.
Approach 2: Dynamic programming
Intuition. A classical alternative that does not search the answer space at all. Let dp[i][j] be the smallest possible largest-piece sum when the first i elements are cut into exactly j pieces. The last piece must be some suffix nums[p .. i-1]; everything before it is a smaller instance of the same problem with j − 1 pieces. Trying every p gives the recurrence
dp[i][j] = min over p of max(dp[p][j-1], sum(nums[p .. i-1])).
Prefix sums make each piece sum O(1).
Algorithm.
- If
k > n, return-1. - Build
prefix[i] = nums[0] + ... + nums[i-1]. - Fill a table of size
(n+1) × (k+1)with “infinity”; setdp[0][0] = 0. - For every
ifrom1tonand everyjfrom1tomin(i, k), try every cut positionpfromj − 1toi − 1(there must be at leastj − 1elements before the last piece) and keep the minimum ofmax(dp[p][j-1], prefix[i] − prefix[p]). - Return
dp[n][k].
static int better(int[] nums, int k) {
int n = nums.length;
if (k > n) return -1;
long[] prefix = new long[n + 1];
for (int i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
long[][] dp = new long[n + 1][k + 1];
for (long[] row : dp) Arrays.fill(row, Long.MAX_VALUE);
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= Math.min(i, k); j++) {
for (int p = j - 1; p < i; p++) { // last piece is nums[p .. i-1]
if (dp[p][j - 1] == Long.MAX_VALUE) continue;
long candidate = Math.max(dp[p][j - 1], prefix[i] - prefix[p]);
dp[i][j] = Math.min(dp[i][j], candidate);
}
}
}
return (int) dp[n][k];
}
Complexity. Time O(k × n^2): for each of the n × k states we scan up to n cut positions. Space O(n × k) for the table.
The DP is worth knowing because it generalises to variants where the greedy check breaks down (for example when pieces have individual costs), but for this exact problem it is slower than what follows, and the n^2 factor hurts on long arrays.
Approach 3: Optimal, binary search on the answer
Intuition. Look at how piecesNeeded behaves as the limit grows: a larger limit can only let pieces hold more, so the number of pieces never increases. That makes the predicate “piecesNeeded(nums, L) <= k” monotone: false for every L below some threshold and true for every L at or above it. A monotone predicate over a range of integers is exactly what binary search finds the boundary of. The brute force scanned the range [max, sum] linearly; binary search covers it in about 30 probes.
Algorithm.
- If
k > n, return-1. - Set
lo = max,hi = sum,ans = sum. - While
lo <= hi: computemid.- If
piecesNeeded(nums, mid) <= k, the limitmidworks; recordans = midand look for something smaller withhi = mid − 1. - Otherwise
midis too tight; setlo = mid + 1.
- If
- Return
ans.
static int optimal(int[] nums, int k) {
int n = nums.length;
if (k > n) return -1;
int max = 0, sum = 0;
for (int x : nums) { max = Math.max(max, x); sum += x; }
int lo = max, hi = sum, ans = sum;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (piecesNeeded(nums, mid) <= k) { ans = mid; hi = mid - 1; }
else lo = mid + 1;
}
return ans;
}
Dry run on nums = [7, 2, 5, 10, 8], k = 2. Here max = 10, sum = 32.
| lo | hi | mid | pieces at limit mid | pieces ≤ 2? | action |
|---|---|---|---|---|---|
| 10 | 32 | 21 | [7,2,5] [10,8] → 2 | yes | ans = 21, hi = 20 |
| 10 | 20 | 15 | [7,2,5] [10] [8] → 3 | no | lo = 16 |
| 16 | 20 | 18 | [7,2,5] [10,8] → 2 | yes | ans = 18, hi = 17 |
| 16 | 17 | 16 | [7,2,5] [10] [8] → 3 | no | lo = 17 |
| 17 | 17 | 17 | [7,2,5] [10] [8] → 3 | no | lo = 18 |
lo > hi, so the answer is 18.
Complexity. Time O(n log(sum)): about log2(sum − max) probes, each a linear pass. Space O(1).
Why is the boundary the true optimum and not just “some achievable limit”? Every achievable limit L corresponds to a real split whose largest piece is at most L, and the smallest achievable L can be met with equality: if the best split had largest piece S < L, then S itself would be achievable, contradicting minimality. So the smallest L passing the check is the exact largest piece sum of an optimal split.
Edge cases and pitfalls
- More pieces than elements. With
k > nsome piece would have to be empty. All three methods return-1before touching the array. k = 1. The only split is the whole array, so the answer is the total. The search starts withlo = max,hi = sum; every probe belowsumneeds 2 or more pieces and the search ends atsum.k = n. Each element gets its own piece and the answer is the maximum element; the very first probe at or abovemaxpasses.- Zeros. A zero never pushes a running sum over the limit, so it never forces a cut.
[0, 0, 0, 5]withk = 2returns5. - Lower bound of the search. Starting at
lo = 0instead ofmaxwould breakpiecesNeeded: an element larger than the limit would be counted as a piece that is already over budget, and the count would be wrong. Always start the search at the largest element. - Overflow. Under the stated constraints the total fits in an
int, butpiecesNeededaccumulates into alonganyway, and the DP useslongtables so thatprefix[i] − prefix[p]and the “infinity” sentinel are safe. If your variant allows a total above2^31 − 1, makelo,hi,midand the return typelongas well.
Related problems
- Book Allocation Problem is the same problem told with books and students, including the
-1fork > n. - Painter’s Partition is the same problem told with boards and painters.
- Capacity to Ship Packages Within D Days is the same greedy partition check with packages and days.
- Koko eating bananas is a gentler first encounter with searching over the answer.
- Aggressive Cows is the maximisation version of binary search on the answer, where the search direction flips.