Skip to content
← All posts

Split Array Largest Sum: Binary Search on the Answer

6 min read
leetcodeproblemhardarraysbinary-searchdynamic-programming

Split Array Largest Sum is LeetCode 410. You are given an integer array nums and an integer k. You need to split nums into k non-empty contiguous subarrays so that the largest sum among those subarrays is minimized. Return that minimized largest sum.

Example: nums = [7, 2, 5, 10, 8], k = 2. The optimal split is [7, 2, 5] and [10, 8], giving sums 14 and 18. The largest is 18, and no other 2-way split produces a smaller largest sum.

Split [7, 2, 5, 10, 8] into k = 2 groups725108Group 1: 7 + 2 + 5 = 14Group 2: 10 + 8 = 18Largest group sum = max(14, 18) = 18Binary search the answer: what is the smallestpossible largest sum? Search range: [10, 32]
Array [7, 2, 5, 10, 8] split into 2 groups. The goal: minimize the largest group sum. The optimal split gives 18.

Why this problem matters

Split Array Largest Sum is one of the best problems for cementing the "binary search on the answer" pattern. You are not searching through an array for a target value. You are searching through all possible answers, finding the smallest one that satisfies a constraint. Once you see this framing, an entire family of hard problems becomes approachable.

This problem also shows up in real systems. Think about splitting a batch of tasks across workers so that no single worker is overloaded. Or distributing pages across volumes so no volume is too thick. Whenever you need to divide a sequence into groups while keeping the worst-case group as small as possible, this is the algorithm.

If you have already solved Koko Eating Bananas, you will recognize the skeleton immediately. The only thing that changes is the feasibility check.

The approach: binary search on the answer

The key insight is that the answer, the minimized largest subarray sum, must fall in a specific range:

  • Lower bound: max(nums). No matter how you split, at least one subarray contains the largest element, so the answer cannot be smaller than that element.
  • Upper bound: sum(nums). If you put everything in one group (which means k = 1), the largest sum is the total sum.

Now notice the monotonic structure. If you can split the array so that every group sum is at most X, you can also do it for X + 1 (just use the same split). If you cannot do it for X, you certainly cannot do it for X - 1. This means the range [max(nums), sum(nums)] splits into "infeasible" on the left and "feasible" on the right, and binary search finds the boundary.

For each candidate mid, you need a feasibility check: can you split nums into k or fewer subarrays such that no subarray sum exceeds mid? This check is greedy. Walk through the array, greedily extending the current subarray. When adding the next element would exceed mid, start a new subarray. Count how many subarrays you need. If it is k or fewer, the candidate is feasible.

def can_split(nums, k, max_sum):
    count = 1
    current = 0
    for num in nums:
        if current + num > max_sum:
            count += 1
            current = num
        else:
            current += num
    return count <= k

Step-by-step walkthrough

Let's binary search for the minimum valid largest sum with nums = [7, 2, 5, 10, 8] and k = 2. The search range is [10, 32], since max(nums) = 10 and sum(nums) = 32.

Step 1: lo=10, hi=32, mid=21. Can we split with max sum 21 or less?

21lo=10hi=32mid=211032feasiblePartition:[7,2,5,10] | [8] = 2 groups

Max group sum is 24... wait, [7,2,5]=14, [10,8]=18. Both are at most 21. Only 2 groups needed. Feasible! Set hi = 21.

Step 2: lo=10, hi=21, mid=15. Can we split with max sum 15 or less?

15lo=10hi=21mid=151032too smallPartition:[7,2,5] | [10] | [8] = 3 groups

[7,2,5]=14 fits. But 10+8=18 > 15, so we must split further. 3 groups > k=2. Not feasible. Set lo = 16.

Step 3: lo=16, hi=21, mid=18. Can we split with max sum 18 or less?

18lo=16hi=21mid=181032feasiblePartition:[7,2,5] | [10,8] = 2 groups

[7,2,5]=14 and [10,8]=18. Both at most 18. 2 groups = k. Feasible! Set hi = 18.

Step 4: lo=16, hi=18, mid=17. Can we split with max sum 17 or less?

17lo=16hi=18mid=171032too smallPartition:[7,2,5] | [10] | [8] = 3 groups

[7,2,5]=14 fits. 10+8=18 > 17, so split: [10],[8]. 3 groups > k=2. Not feasible. Set lo = 18.

Step 5: lo=18, hi=18. lo equals hi. The answer is 18.

18lo=18hi=18mid=181032answerPartition:[7,2,5] | [10,8] = 2 groups

Search complete. The minimum possible largest sum when splitting into 2 groups is 18.

Four real iterations. Instead of checking all 23 possible values in the range, we checked 4 and found the answer. For larger ranges the savings are dramatic. Binary search on a range up to a billion still takes about 30 steps.

The complete solution

Here is the full Python solution for LeetCode 410:

def split_array(nums, k):
    lo, hi = max(nums), sum(nums)

    while lo < hi:
        mid = lo + (hi - lo) // 2
        count = 1
        current = 0
        for num in nums:
            if current + num > mid:
                count += 1
                current = num
            else:
                current += num

        if count <= k:
            hi = mid
        else:
            lo = mid + 1

    return lo

Let's walk through the important decisions.

lo = max(nums), hi = sum(nums). The tightest possible bounds. The answer is guaranteed to be somewhere in this range.

while lo < hi, not while lo <= hi. We are converging on a single answer. When lo == hi, that is our answer.

The greedy scan inside the loop. We walk through nums, accumulating a running sum. When adding the next element would push the sum past mid, we start a new group. This greedy approach always uses the fewest groups possible for a given mid, which is exactly what we need.

if count <= k: hi = mid. If we can split into k or fewer groups, the candidate works, but a smaller value might also work. So we keep mid in range and search left.

else: lo = mid + 1. If we need more than k groups, the candidate is too small. We need a larger max sum.

A common mistake is setting hi = mid - 1 when the candidate is feasible. You cannot do that, because mid itself might be the optimal answer. If you skip it, you could miss the correct value. Always use hi = mid when the current candidate is still potentially optimal.

Complexity analysis

ApproachTimeSpace
Binary search + greedyO(n * log(sum - max))O(1)
Dynamic programmingO(n^2 * k)O(n * k)

The binary search runs O(log(sum(nums) - max(nums))) iterations. Each iteration does a linear scan of the array in O(n). That gives O(n * log S) total, where S is the sum of the array.

The DP approach uses a 2D table where dp[i][j] is the answer for the first i elements split into j groups. It works, but it is much slower for large inputs. The binary search solution is both faster and simpler to implement.

Edge cases to watch for

  • k = 1: No splitting at all. The answer is sum(nums).
  • k = len(nums): Every element is its own group. The answer is max(nums).
  • Single element: nums = [10], k = 1. The answer is 10.
  • All elements equal: nums = [5, 5, 5, 5], k = 2. Split into [5, 5] and [5, 5], answer is 10.
  • k equals array length: Each element stands alone, so max(nums) is the answer. The binary search handles this naturally because lo starts at max(nums).

The building blocks

This problem is built on one core pattern: binary search on the answer.

The skeleton is identical to what you see in Koko Eating Bananas and Capacity to Ship Packages (LeetCode 1011). Define a range of candidate answers, write a feasibility check, and binary search for the boundary.

while lo < hi:
    mid = lo + (hi - lo) // 2
    if is_feasible(mid):
        hi = mid
    else:
        lo = mid + 1
return lo

What changes from problem to problem is the feasibility function. For Koko, you check if she can eat all bananas at speed mid within h hours. For Split Array Largest Sum, you check if you can partition the array into k or fewer groups where each group sums to at most mid. The binary search wrapper stays the same.

The feasibility check itself uses a greedy scan, which is a second building block. You walk through the array left to right, making the locally optimal choice at each step (extend the current group as long as possible). This greedy approach works because extending a group never makes things worse for future groups.

From understanding to recall

The logic clicks quickly. Binary search the answer range, greedily check each midpoint, narrow down. You probably see it clearly right now.

But try writing it from memory in two weeks. Was it lo < hi or lo <= hi? Do you set hi = mid or hi = mid - 1? What are the initial bounds, max(nums) and sum(nums) or something else? And the greedy check, do you start count at 0 or 1? These small details are easy to get wrong under pressure.

Spaced repetition closes the gap. You write the solution from scratch today, again tomorrow, then in a few days. After a few rounds, the while lo < hi template and the greedy partition check are automatic. You write them without hesitation and save your mental energy for problems that actually require new thinking.

Related posts