AlgoMaster Logo

Split Array Largest Sum

hardUpdated September 21, 2026

Understanding the Problem

We have an array and we need to split it into exactly k contiguous groups. Each group has a sum, and we want to minimize the maximum of those sums. Think of it like distributing work across k workers who must each handle a contiguous chunk, and we want to make the heaviest workload as light as possible.

The constraint that matters is that the subarrays must be contiguous. We cannot pick arbitrary elements for a group. We only choose where to place k - 1 dividers in the array. With n - 1 possible divider positions and k - 1 dividers to place, the number of ways to split grows combinatorially.

The goal is not to make all subarrays equal. It is to minimize the worst case, the single largest subarray sum. This minimax structure is what allows both a dynamic programming and a binary search solution.

Key Constraints:

  • 1 <= nums.length <= 1000. With n up to 1000, the O(n^2 k) dynamic programming solution runs about 50 million operations in the worst case, which is tight but feasible. The O(n log(sum)) binary search is far faster.
  • 0 <= nums[i] <= 10^6. Elements can be zero, so some subarrays can have a sum of zero. The total sum reaches up to 10^9, which exceeds the 32-bit integer range, so the running sum and search bounds need a 64-bit type in languages like Java, C++, Go, C#, and Rust.
  • 1 <= k <= min(50, nums.length). k is small relative to n, which keeps the DP table dimension manageable.

Approach 1: Brute Force (Backtracking)

Intuition

Try every possible way of splitting the array. We place k - 1 dividers among the n - 1 gaps between elements, and for each configuration we compute the sum of each subarray, take the maximum, and track the overall minimum.

Recursion expresses this directly. At each step we decide where the current subarray ends, compute its sum, and recurse on the remaining array with one fewer split to make. The base case is when one split remains, where the entire remaining array forms the last subarray.

A prefix sum array lets us compute any subarray sum in O(1), which avoids re-adding elements on every recursive call.

Algorithm

  1. Build a prefix sum array so we can compute any subarray sum in O(1).
  2. Define a recursive function solve(index, splitsLeft) that returns the minimum possible largest sum when splitting nums[index..n-1] into splitsLeft subarrays.
  3. Base case: if splitsLeft == 1, return the sum of nums[index..n-1].
  4. For each possible end position of the first subarray (from index to n - splitsLeft), compute the sum of the current subarray, recursively solve the rest, and take the maximum of the two. Track the minimum across all choices.
  5. Return the result of solve(0, k).

Visualization and Code

Loading animation...

The recursion recomputes the same subproblems repeatedly. The state solve(index, splitsLeft) depends only on those two values, yet it can be reached from many different earlier split choices. Caching each (index, splitsLeft) pair collapses the work to one computation per state, which is the basis for the dynamic programming approach.

Approach 2: Dynamic Programming

Intuition

There are only n * k distinct states, one for each pair of array position and remaining split count, so caching turns the exponential recursion into a polynomial table fill.

Building the table bottom-up, let dp[j][i] represent the minimum possible largest sum when splitting the first i elements into j subarrays. To fill in dp[j][i], we try every possible position p for the start of the last subarray. The last subarray covers nums[p..i-1], and the first j-1 subarrays cover nums[0..p-1]. So dp[j][i] = min over all valid p of max(dp[j-1][p], sum(p..i-1)).

The base case is dp[1][i] = sum of the first i elements, because with one subarray, we must take everything.

Algorithm

  1. Build a prefix sum array for O(1) range sum queries.
  2. Create a 2D DP table of size (k+1) x (n+1), initialized to infinity.
  3. Set the base case: dp[1][i] = prefix[i] for all i from 1 to n.
  4. For each number of splits j from 2 to k:
    • For each number of elements i from j to n (need at least j elements for j splits):
      • For each possible start of the last subarray p from j-1 to i-1:
        • lastSum = prefix[i] - prefix[p]
        • dp[j][i] = min(dp[j][i], max(dp[j-1][p], lastSum))
  5. Return dp[k][n].

Visualization and Code

Loading animation...

The DP enumerates every split position. The answer itself is a single number within a bounded range, and the next approach searches that range directly, checking feasibility instead of constructing the split.

Approach 3: Binary Search on Answer (Optimal)

Intuition

Instead of asking "what is the best way to split?", flip the question to "given a maximum allowed subarray sum of X, can we split the array into k or fewer subarrays?"

That feasibility question has a greedy answer. Scan left to right, extending the current subarray as far as possible. When adding the next element would push the running sum past X, start a new subarray. Count the subarrays needed. If the count is at most k, then X is achievable.

The feasibility function is monotonic. If a max sum of X permits a valid split, then X + 1 permits the same split, so it is also feasible. If X does not permit a valid split, neither does X - 1. Feasibility flips from false to true at a single threshold, and that threshold is the answer, so binary search on X finds it.

The search range is [max(nums), sum(nums)]. The lower bound is max(nums), because the largest single element must fit inside some subarray no matter how the array is split. The upper bound is sum(nums), the value when k = 1 and one subarray holds everything.

Algorithm

  1. Compute left = max(nums) and right = sum(nums).
  2. Binary search: while left < right:
    • mid = left + (right - left) / 2
    • Check if we can split into at most k subarrays where each has sum at most mid.
    • If yes, try a smaller value: right = mid.
    • If no, we need a larger limit: left = mid + 1.
  3. The feasibility check (canSplit): scan left to right, maintaining a running sum. Whenever adding the next element would exceed mid, start a new subarray. Count the subarrays needed.
  4. Return left.

Visualization and Code

Loading animation...