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:
- The array is divided into exactly
k
parts - Each part is a contiguous subarray (elements stay in their original order)
- 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.
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 (whenk = 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 Approach
The solution uses binary search combined with a greedy validation function to find the minimum possible largest sum.
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) - Use
bisect_left
to find the leftmost position where our condition becomesTrue
Validation Function (check
):
The check(mx)
function determines if we can split the array into at most k
subarrays where no subarray sum exceeds mx
:
- Initialize
s = inf
(starting with infinity ensures we immediately start a new subarray) andcnt = 0
(count of subarrays) - For each element
x
innums
:- Add
x
to the current sum:s += x
- If
s > mx
, we've exceeded our limit:- Start a new subarray with just
x
:s = x
- Increment the subarray count:
cnt += 1
- Start a new subarray with just
- Add
- Return
cnt <= k
to check if we used at mostk
subarrays
Binary Search Logic:
The expression left + bisect_left(range(left, right + 1), True, key=check)
works as follows:
range(left, right + 1)
creates our search spacekey=check
applies the validation function to each middle valuebisect_left
finds the first position wherecheck(mx)
returnsTrue
- Adding
left
to the result gives us the actual value (sincebisect_left
returns an index starting from 0)
Why This Works:
- If a maximum sum
mx
allows splitting into at mostk
subarrays, any larger maximum sum will also work (but won't be minimal) - If a maximum sum
mx
requires more thank
subarrays, we need to increasemx
- Binary search efficiently finds the boundary where we transition from "not possible" to "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 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [7,2,5,10,8]
and k = 2
.
Step 1: Set up binary search bounds
left = max(nums) = 10
(minimum possible largest sum)right = sum(nums) = 32
(maximum possible largest sum)- Search space: [10, 11, 12, ..., 32]
Step 2: Binary search process
Let's trace through a few iterations:
Check mid = 21
(midpoint of [10, 32])
- Start with
s = inf
,cnt = 0
- Process element 7:
s = inf + 7 > 21
, so start new subarray:s = 7
,cnt = 1
- Process element 2:
s = 7 + 2 = 9 ≤ 21
, continue - Process element 5:
s = 9 + 5 = 14 ≤ 21
, continue - Process element 10:
s = 14 + 10 = 24 > 21
, start new subarray:s = 10
,cnt = 2
- Process element 8:
s = 10 + 8 = 18 ≤ 21
, continue - Result:
cnt = 2 ≤ k = 2
✓ (Valid, but can we go lower?)
Check mid = 15
(searching lower half)
- Start with
s = inf
,cnt = 0
- Process element 7:
s = inf + 7 > 15
, so start new subarray:s = 7
,cnt = 1
- Process element 2:
s = 7 + 2 = 9 ≤ 15
, continue - Process element 5:
s = 9 + 5 = 14 ≤ 15
, continue - Process element 10:
s = 14 + 10 = 24 > 15
, start new subarray:s = 10
,cnt = 2
- Process element 8:
s = 10 + 8 = 18 > 15
, start new subarray:s = 8
,cnt = 3
- Result:
cnt = 3 > k = 2
✗ (Invalid, need higher max sum)
Check mid = 18
(between 15 and 21)
- Start with
s = inf
,cnt = 0
- Process element 7:
s = inf + 7 > 18
, so start new subarray:s = 7
,cnt = 1
- Process element 2:
s = 7 + 2 = 9 ≤ 18
, continue - Process element 5:
s = 9 + 5 = 14 ≤ 18
, continue - Process element 10:
s = 14 + 10 = 24 > 18
, start new subarray:s = 10
,cnt = 2
- Process element 8:
s = 10 + 8 = 18 ≤ 18
, continue - Result:
cnt = 2 ≤ k = 2
✓ (Valid)
Check mid = 17
- Following similar logic, we'd find
cnt = 3 > k = 2
✗ (Invalid)
Step 3: Binary search finds the answer The binary search determines that 18 is the minimum value where we can split the array into at most 2 subarrays.
The final split would be: [7,2,5]
(sum = 14) and [10,8]
(sum = 18), giving us a maximum sum of 18.
Solution Implementation
1class Solution:
2 def splitArray(self, nums: List[int], k: int) -> int:
3 def can_split_with_max_sum(max_sum_limit):
4 """
5 Check if we can split the array into at most k subarrays
6 where each subarray sum doesn't exceed max_sum_limit.
7
8 Args:
9 max_sum_limit: The maximum allowed sum for any subarray
10
11 Returns:
12 True if we can split into at most k subarrays, False otherwise
13 """
14 current_sum = float('inf') # Initialize to infinity to force first element into new subarray
15 subarray_count = 0
16
17 for num in nums:
18 current_sum += num
19
20 # If adding current number exceeds limit, start a new subarray
21 if current_sum > max_sum_limit:
22 current_sum = num
23 subarray_count += 1
24
25 # Check if number of subarrays is within limit
26 return subarray_count <= k
27
28 # Binary search bounds:
29 # Minimum possible max sum: largest single element (when k = len(nums))
30 # Maximum possible max sum: sum of all elements (when k = 1)
31 min_possible = max(nums)
32 max_possible = sum(nums)
33
34 # Binary search for the minimum valid maximum subarray sum
35 # bisect_left finds the leftmost position where can_split_with_max_sum returns True
36 return min_possible + bisect_left(
37 range(min_possible, max_possible + 1),
38 True,
39 key=can_split_with_max_sum
40 )
41
1class Solution {
2 /**
3 * Splits an array into k subarrays to minimize the largest sum among all subarrays.
4 * Uses binary search on the answer space.
5 *
6 * @param nums The input array to be split
7 * @param k The number of subarrays to split into
8 * @return The minimized largest sum among all subarrays
9 */
10 public int splitArray(int[] nums, int k) {
11 // Initialize binary search bounds
12 // left: minimum possible answer (max element in array)
13 // right: maximum possible answer (sum of all elements)
14 int left = 0;
15 int right = 0;
16
17 for (int num : nums) {
18 left = Math.max(left, num); // Find the maximum element
19 right += num; // Calculate total sum
20 }
21
22 // Binary search for the minimum valid maximum sum
23 while (left < right) {
24 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
25
26 // Check if we can split array into at most k subarrays
27 // with each subarray sum <= mid
28 if (canSplit(nums, mid, k)) {
29 right = mid; // Try to find a smaller maximum sum
30 } else {
31 left = mid + 1; // Current mid is too small, increase it
32 }
33 }
34
35 return left;
36 }
37
38 /**
39 * Checks if the array can be split into at most k subarrays
40 * where each subarray sum does not exceed maxSum.
41 *
42 * @param nums The input array
43 * @param maxSum The maximum allowed sum for each subarray
44 * @param k The maximum number of subarrays allowed
45 * @return true if valid split is possible, false otherwise
46 */
47 private boolean canSplit(int[] nums, int maxSum, int k) {
48 // Initialize with a large value to force first element into first subarray
49 int currentSum = 1 << 30; // Large initial value (2^30)
50 int subarrayCount = 0; // Number of subarrays needed
51
52 for (int num : nums) {
53 currentSum += num;
54
55 // If adding current element exceeds maxSum, start a new subarray
56 if (currentSum > maxSum) {
57 subarrayCount++;
58 currentSum = num; // Start new subarray with current element
59 }
60 }
61
62 // Check if number of subarrays needed is within limit
63 return subarrayCount <= k;
64 }
65}
66
1class Solution {
2public:
3 int splitArray(vector<int>& nums, int k) {
4 // Initialize binary search bounds
5 // left: minimum possible sum (largest single element)
6 // right: maximum possible sum (sum of all elements)
7 int left = 0, right = 0;
8 for (int& num : nums) {
9 left = max(left, num);
10 right += num;
11 }
12
13 // Lambda function to check if we can split array into at most k subarrays
14 // with each subarray sum not exceeding maxSum
15 auto canSplit = [&](int maxSum) {
16 int currentSum = 0; // Current subarray sum
17 int splitCount = 1; // Number of subarrays (start with 1)
18
19 for (int& num : nums) {
20 // If adding current number exceeds maxSum, start a new subarray
21 if (currentSum + num > maxSum) {
22 currentSum = num; // Start new subarray with current number
23 ++splitCount; // Increment split count
24
25 // If we need more than k subarrays, this maxSum is too small
26 if (splitCount > k) {
27 return false;
28 }
29 } else {
30 currentSum += num; // Add to current subarray
31 }
32 }
33
34 return true; // Can split with at most k subarrays
35 };
36
37 // Binary search for the minimum largest sum
38 while (left < right) {
39 int mid = left + (right - left) / 2;
40
41 if (canSplit(mid)) {
42 // If we can split with mid as max sum, try smaller value
43 right = mid;
44 } else {
45 // If we cannot split with mid as max sum, need larger value
46 left = mid + 1;
47 }
48 }
49
50 return left; // Minimum largest sum among all k subarrays
51 }
52};
53
1/**
2 * Splits an array into k subarrays such that the maximum sum among all subarrays is minimized
3 * @param nums - The input array of numbers to split
4 * @param k - The number of subarrays to create
5 * @returns The minimized largest sum among all k subarrays
6 */
7function splitArray(nums: number[], k: number): number {
8 // Left boundary: minimum possible max sum (largest single element)
9 let left: number = Math.max(...nums);
10
11 // Right boundary: maximum possible max sum (sum of all elements)
12 let right: number = nums.reduce((accumulator: number, current: number) => accumulator + current, 0);
13
14 /**
15 * Checks if we can split the array into at most k subarrays
16 * where each subarray sum doesn't exceed maxSum
17 * @param maxSum - The maximum allowed sum for each subarray
18 * @returns true if splitting is possible, false otherwise
19 */
20 const canSplit = (maxSum: number): boolean => {
21 let currentSum: number = 0;
22 let subarrayCount: number = 0;
23
24 // Iterate through each number in the array
25 for (const num of nums) {
26 currentSum += num;
27
28 // If current sum exceeds maxSum, start a new subarray
29 if (currentSum > maxSum) {
30 currentSum = num;
31 subarrayCount++;
32
33 // If we need more than k subarrays, this maxSum is too small
34 if (subarrayCount === k) {
35 return false;
36 }
37 }
38 }
39
40 return true;
41 };
42
43 // Binary search to find the minimum possible maximum sum
44 while (left < right) {
45 const mid: number = Math.floor((left + right) / 2);
46
47 if (canSplit(mid)) {
48 // If we can split with this max sum, try a smaller one
49 right = mid;
50 } else {
51 // If we cannot split with this max sum, we need a larger one
52 left = mid + 1;
53 }
54 }
55
56 return left;
57}
58
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 variabless
,cnt
, andx
which requireO(1)
space - The binary search uses variables
left
andright
which requireO(1)
space - The
bisect_left
function with a range object doesn't create the entire range in memory but generates values on-the-fly, usingO(1)
space
Therefore, the space complexity is O(1)
.
Common Pitfalls
1. Incorrect Binary Search Bounds
A common mistake is setting incorrect bounds for the binary search. Some developers might use:
left = 0
orleft = min(nums)
instead ofleft = max(nums)
- This fails because each subarray must contain at least one element, so the minimum possible maximum sum is the largest single element in the array
Why this matters: If nums = [10, 5, 13]
and k = 3
, the answer should be 13 (each element in its own subarray). Using left = 0
or left = 5
would cause the binary search to check invalid values.
2. Off-by-One Error in Subarray Counting
The initialization current_sum = float('inf')
and subarray_count = 0
might seem counterintuitive. A common mistake is to initialize:
current_sum = 0 subarray_count = 1 # Starting with one subarray
Why the original is correct: Starting with current_sum = inf
ensures the first element immediately triggers a new subarray creation, making the counting logic consistent throughout the iteration. The alternative initialization requires special handling for the last subarray.
3. Confusion Between "At Most k" vs "Exactly k"
The validation function checks for "at most k subarrays" (subarray_count <= k
), not exactly k. Some might incorrectly use:
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 those subarrays to create exactly k subarrays without increasing the maximum sum. The binary search finds the minimum value where splitting into at most k subarrays is possible.
4. Greedy Algorithm Assumption
The greedy approach (always starting a new subarray when the sum exceeds the limit) works here, but developers might question why we don't need dynamic programming or backtracking.
Why greedy works: For a fixed maximum sum limit, the greedy strategy of creating a new subarray as late as possible (only when we must) minimizes the number of subarrays needed. This is optimal because delaying the split allows us to potentially fit more elements in each subarray.
Solution to Avoid These Pitfalls:
class Solution:
def splitArray(self, nums: List[int], k: int) -> int:
def can_split(max_sum):
# Clear naming and documentation
"""Returns True if array can be split into at most k subarrays
with each sum <= max_sum"""
# Explicit initialization with clear intent
subarrays_needed = 1 # We need at least one subarray
current_sum = 0
for num in nums:
if current_sum + num > max_sum:
# Must start a new subarray
subarrays_needed += 1
current_sum = num
# Early termination if we need too many subarrays
if subarrays_needed > k:
return False
else:
current_sum += num
return True
# Validate input constraints
if k > len(nums):
return max(nums) # Each element in its own subarray
# Clear bound definitions with comments
left = max(nums) # Minimum: largest element (can't split an element)
right = sum(nums) # Maximum: entire array as one subarray
# Standard binary search pattern
while left < right:
mid = left + (right - left) // 2
if can_split(mid):
right = mid # Try to find smaller maximum
else:
left = mid + 1 # Need larger maximum
return left
This alternative implementation makes the logic more explicit and easier to verify, avoiding the common pitfalls while maintaining the same time complexity.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!