Facebook Pixel

410. Split Array Largest Sum

Problem Description

You are given an integer array nums and an integer k. Your task is to split the array nums into exactly k non-empty subarrays in a way that minimizes the largest sum among all the subarrays.

A subarray is a contiguous portion of the original array, meaning the elements must appear consecutively in their original order.

The goal is to find a splitting strategy where:

  1. The array is divided into exactly k parts
  2. Each part is a contiguous subarray (elements stay in their original order)
  3. Among all possible ways to split the array into k parts, you want the one where the maximum sum of any subarray is as small as possible

For example, if you have nums = [7,2,5,10,8] and k = 2, you could split it as:

  • [7,2,5] and [10,8] with sums 14 and 18, so the largest sum is 18
  • [7,2] and [5,10,8] with sums 9 and 23, so the largest sum is 23
  • [7] and [2,5,10,8] with sums 7 and 25, so the largest sum is 25

Among these options (and others), the first split gives the minimum possible largest sum of 18.

The function should return this minimized largest sum value.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that there's a relationship between the maximum allowed sum for subarrays and the number of subarrays we need to create. If we allow a larger maximum sum per subarray, we can fit more elements into each subarray, resulting in fewer total subarrays. Conversely, if we restrict the maximum sum to be smaller, we'll need more subarrays to accommodate all elements.

Think of it like packing items into boxes with a weight limit - the higher the weight limit per box, the fewer boxes you need.

This relationship is monotonic: as we increase the maximum allowed sum, the number of required subarrays either stays the same or decreases. This monotonic property is perfect for binary search.

We can establish bounds for our search:

  • The minimum possible largest sum is max(nums) - we can't go lower than this because at least one subarray must contain the largest element
  • The maximum possible largest sum is sum(nums) - this happens when we put all elements in a single subarray (when k = 1)

For any candidate value mid between these bounds, we can greedily check if it's possible to split the array into at most k subarrays where no subarray sum exceeds mid. We traverse the array, accumulating elements into the current subarray. When adding another element would exceed mid, we start a new subarray. If we can do this with k or fewer subarrays, then mid is a valid maximum sum.

Since we want the minimum possible value of this maximum sum, we use binary search to efficiently find the smallest mid that allows a valid split into at most k subarrays.

Solution Implementation

1class Solution:
2    def splitArray(self, nums: List[int], k: int) -> int:
3        def feasible(max_sum):
4            """
5            Check if we can split the array into at most k subarrays
6            where each subarray sum doesn't exceed max_sum.
7            """
8            current_sum = 0
9            subarray_count = 1  # Start with one subarray
10
11            for num in nums:
12                if current_sum + num > max_sum:
13                    # Start a new subarray
14                    current_sum = num
15                    subarray_count += 1
16                else:
17                    current_sum += num
18
19            return subarray_count <= k
20
21        # Binary search bounds
22        left, right = max(nums), sum(nums)
23        first_true_index = -1
24
25        while left <= right:
26            mid = (left + right) // 2
27            if feasible(mid):
28                first_true_index = mid
29                right = mid - 1
30            else:
31                left = mid + 1
32
33        return first_true_index
34
1class Solution {
2    public int splitArray(int[] nums, int k) {
3        // Initialize binary search bounds
4        int left = 0;
5        int right = 0;
6
7        for (int num : nums) {
8            left = Math.max(left, num);
9            right += num;
10        }
11
12        int firstTrueIndex = -1;
13
14        while (left <= right) {
15            int mid = left + (right - left) / 2;
16            if (feasible(nums, mid, k)) {
17                firstTrueIndex = mid;
18                right = mid - 1;
19            } else {
20                left = mid + 1;
21            }
22        }
23
24        return firstTrueIndex;
25    }
26
27    private boolean feasible(int[] nums, int maxSum, int k) {
28        int currentSum = 0;
29        int subarrayCount = 1;
30
31        for (int num : nums) {
32            if (currentSum + num > maxSum) {
33                currentSum = num;
34                subarrayCount++;
35            } else {
36                currentSum += num;
37            }
38        }
39
40        return subarrayCount <= k;
41    }
42}
43
1class Solution {
2public:
3    int splitArray(vector<int>& nums, int k) {
4        int left = 0, right = 0;
5        for (int& num : nums) {
6            left = max(left, num);
7            right += num;
8        }
9
10        auto feasible = [&](int maxSum) {
11            int currentSum = 0;
12            int subarrayCount = 1;
13
14            for (int& num : nums) {
15                if (currentSum + num > maxSum) {
16                    currentSum = num;
17                    subarrayCount++;
18                } else {
19                    currentSum += num;
20                }
21            }
22
23            return subarrayCount <= k;
24        };
25
26        int firstTrueIndex = -1;
27
28        while (left <= right) {
29            int mid = left + (right - left) / 2;
30            if (feasible(mid)) {
31                firstTrueIndex = mid;
32                right = mid - 1;
33            } else {
34                left = mid + 1;
35            }
36        }
37
38        return firstTrueIndex;
39    }
40};
41
1function splitArray(nums: number[], k: number): number {
2    let left: number = Math.max(...nums);
3    let right: number = nums.reduce((acc, num) => acc + num, 0);
4
5    const feasible = (maxSum: number): boolean => {
6        let currentSum: number = 0;
7        let subarrayCount: number = 1;
8
9        for (const num of nums) {
10            if (currentSum + num > maxSum) {
11                currentSum = num;
12                subarrayCount++;
13            } else {
14                currentSum += num;
15            }
16        }
17
18        return subarrayCount <= k;
19    };
20
21    let firstTrueIndex: number = -1;
22
23    while (left <= right) {
24        const mid: number = Math.floor((left + right) / 2);
25        if (feasible(mid)) {
26            firstTrueIndex = mid;
27            right = mid - 1;
28        } else {
29            left = mid + 1;
30        }
31    }
32
33    return firstTrueIndex;
34}
35

Solution Approach

The solution uses binary search combined with a greedy validation function to find the minimum possible largest sum. We apply the standard binary search template to find the first value where splitting is feasible.

Binary Search Setup:

  • Initialize left = max(nums) as the lower bound (smallest possible maximum sum)
  • Initialize right = sum(nums) as the upper bound (largest possible maximum sum)
  • Initialize first_true_index = -1 to track the answer

Feasible Function: The feasible(max_sum) function determines if we can split the array into at most k subarrays where no subarray sum exceeds max_sum. This creates a boolean pattern over our search space:

max_sum:    10   11   12   ...  17   18   19   ...  32
feasible: false false false ... false true true ... true

We want to find the first true position.

The checking logic:

  1. Initialize current_sum = 0 and subarray_count = 1 (we start with one subarray)
  2. For each element in nums:
    • If adding the element exceeds max_sum, start a new subarray
    • Otherwise, add it to the current subarray
  3. Return subarray_count <= k

Binary Search Template:

left, right = max(nums), sum(nums)
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1  # Try to find a smaller valid value
    else:
        left = mid + 1   # Current value is too small

return first_true_index

Why This Works:

  • If a maximum sum mx allows splitting into at most k subarrays, we record it and try smaller values
  • If a maximum sum mx requires more than k subarrays, we need to increase mx
  • Binary search efficiently finds the minimum value where splitting is possible

Time Complexity: O(n * log(sum(nums) - max(nums))) where n is the length of the array Space Complexity: O(1) as we only use a constant amount of extra space

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with nums = [7,2,5,10,8] and k = 2.

Step 1: Set up binary search

  • left = max(nums) = 10 (minimum possible largest sum)
  • right = sum(nums) = 32 (maximum possible largest sum)
  • first_true_index = -1 (tracks the answer)

Step 2: Binary search with template

Iteration 1: left=10, right=32

  • mid = (10 + 32) // 2 = 21
  • Check feasible(21):
    • current_sum = 0, count = 1
    • Add 7: current_sum = 7
    • Add 2: current_sum = 9
    • Add 5: current_sum = 14
    • Add 10: 14 + 10 = 24 > 21, start new subarray: current_sum = 10, count = 2
    • Add 8: current_sum = 18
    • Result: count = 2 ≤ k = 2True
  • feasible(21) = True, so first_true_index = 21, right = 20

Iteration 2: left=10, right=20

  • mid = (10 + 20) // 2 = 15
  • Check feasible(15):
    • After processing: needs 3 subarrays (14, 10, 8)
    • Result: count = 3 > k = 2False
  • feasible(15) = False, so left = 16

Iteration 3: left=16, right=20

  • mid = (16 + 20) // 2 = 18
  • Check feasible(18):
    • Subarrays: [7,2,5] (sum=14), [10,8] (sum=18)
    • Result: count = 2 ≤ k = 2True
  • feasible(18) = True, so first_true_index = 18, right = 17

Iteration 4: left=16, right=17

  • mid = (16 + 17) // 2 = 16
  • feasible(16) = False (needs 3 subarrays)
  • left = 17

Iteration 5: left=17, right=17

  • mid = 17
  • feasible(17) = False (needs 3 subarrays)
  • left = 18

Loop ends: left = 18 > right = 17

Answer: first_true_index = 18

The final split: [7,2,5] (sum = 14) and [10,8] (sum = 18).

Time and Space Complexity

Time Complexity: O(n × log m)

The algorithm uses binary search on the range [max(nums), sum(nums)]. The search space has a size of sum(nums) - max(nums) + 1, which is at most m (where m = sum(nums)). Binary search over this range takes O(log m) iterations.

For each binary search iteration, the check function is called, which iterates through all n elements of the array once, taking O(n) time.

Therefore, the overall time complexity is O(n × log m), where n is the length of the array and m is the sum of all elements in the array.

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space:

  • The check function uses variables s, cnt, and x which require O(1) space
  • The binary search uses variables left and right which require O(1) space
  • The bisect_left function with a range object doesn't create the entire range in memory but generates values on-the-fly, using O(1) space

Therefore, the space complexity is O(1).

Common Pitfalls

1. Using Wrong Binary Search Template Variant

A common mistake is using while left < right with right = mid instead of the standard template with first_true_index.

Incorrect:

while left < right:
    mid = (left + right) // 2
    if feasible(mid):
        right = mid
    else:
        left = mid + 1
return left

Correct (template-compliant):

first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
return first_true_index

The template approach is more explicit about tracking the answer and handles edge cases consistently.

2. Incorrect Binary Search Bounds

A common mistake is setting incorrect bounds:

  • Using left = 0 or left = min(nums) instead of left = max(nums)
  • The minimum possible maximum sum is the largest single element (each subarray must have at least one element)

Why this matters: If nums = [10, 5, 13] and k = 3, the answer should be 13. Using left = 0 would waste iterations on invalid values.

3. Confusion Between "At Most k" vs "Exactly k"

The feasible function checks for "at most k subarrays" (subarray_count <= k), not exactly k.

Incorrect:

return subarray_count == k

Why "at most" works: If we can split into fewer than k subarrays with a given maximum sum, we can always further split to create exactly k subarrays without increasing the maximum sum.

4. Greedy Algorithm Correctness

The greedy approach (starting a new subarray only when sum exceeds limit) is optimal for a fixed maximum sum limit. Delaying the split allows more elements per subarray, minimizing the total count.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How many ways can you arrange the three letters A, B and C?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More