Loading problems...
TL;DR: The optimal solution uses binary search on the answer space (the range of possible sums) combined with a greedy verification strategy to determine if a specific sum capacity is feasible.
The Split Array Largest Sum problem asks us to divide an integer array nums into k non-empty, contiguous subarrays. The goal is to minimize the largest sum among these k subarrays. This is a classic minimax problem—minimizing the maximum value—which is a strong signal for specific algorithmic patterns.
This problem, LeetCode 410, is a popular interview question at top-tier tech companies because it tests the ability to recognize when to search the answer space rather than the input array itself.
The naive approach involves trying every possible way to place k-1 dividers into the array to create k subarrays. We would then calculate the maximum sum for each configuration and find the global minimum.
This can be visualized as a Depth First Search (DFS) where we recursively decide where to end the current subarray.
textfunction bruteForce(index, subarraysLeft): if subarraysLeft == 1: return sum(nums[index...end]) minLargestSum = infinity currentSum = 0 for i from index to n - subarraysLeft: currentSum += nums[i] remainingMax = bruteForce(i + 1, subarraysLeft - 1) currentMax = max(currentSum, remainingMax) minLargestSum = min(minLargestSum, currentMax) return minLargestSum
Complexity Analysis:
The time complexity is roughly proportional to the number of combinations of choosing k-1 split points among N-1 positions, which is . In the worst case, this is exponential. Given the constraint , an exponential solution will result in a Time Limit Exceeded (TLE) error.
The core insight is to invert the problem. Instead of asking "What is the minimized largest sum?", we ask: "Given a candidate maximum sum S, can we split the array into k or fewer subarrays such that no subarray sum exceeds S?"
This boolean question—let's call it canSplit(S)—is much easier to solve. We can determine canSplit(S) using a greedy approach in linear time.
Because the property is monotonic, we can binary search for the smallest S where canSplit(S) returns true.
left): The smallest possible value for the largest sum is the maximum single element in nums. We cannot have a subarray sum smaller than the largest element itself.right): The largest possible value is the sum of all elements in nums (the case where ).Visual Description: Imagine a number line representing all possible values for the "largest sum".
k subarrays to fit all numbers. The condition fails.k (or fewer) subarrays. The condition passes.
Let's trace nums = [7, 2, 5, 10, 8] with k = 2.
Initialization:
left = 10 (max element).right = 32 (sum of all elements).Iteration 1:
mid = (10 + 32) / 2 = 21.canSplit(21):
[7, 2, 5] (Sum 14). Adding 10 exceeds 21. Split.[10, 8] (Sum 18).canSplit returns True.mid is valid. We try to find a smaller valid sum.right becomes 20.Iteration 2:
mid = (10 + 20) / 2 = 15.canSplit(15):
[7, 2, 5] (Sum 14). Adding 10 exceeds 15. Split.[10]. Adding 8 exceeds 15. Split.[8].canSplit returns False.mid is too small.left becomes 16.Iteration 3:
mid = (16 + 20) / 2 = 18.canSplit(18):
[7, 2, 5] (Sum 14). Split.[10, 8] (Sum 18).right becomes 17.Iteration 4:
mid = (16 + 17) / 2 = 16.canSplit(16): Returns False (needs 3 subarrays).left becomes 17.Iteration 5:
mid = (17 + 17) / 2 = 17.canSplit(17): Returns False (needs 3 subarrays).left becomes 18.Termination:
left (18) > right (17). Loop ends.left = 18.The correctness relies on two properties:
C is valid, any capacity C' > C is also valid (we can always split less or have smaller sums). If C is invalid, any C' < C is also invalid. This justifies Binary Search.canSplit, we delay starting a new subarray as long as possible. This ensures we use the minimum number of subarrays for a given capacity. If the minimum number of subarrays needed for capacity X is , then X is a feasible solution (we can always arbitrarily split more to reach exactly k without increasing the max sum).Therefore, the algorithm finds the minimal S such that the greedy decomposition results in subarrays.
cppclass Solution { public: int splitArray(vector<int>& nums, int k) { long long left = 0; long long right = 0; // Initialize bounds for (int num : nums) { left = max(left, (long long)num); right += num; } while (left <= right) { long long mid = left + (right - left) / 2; if (canSplit(nums, k, mid)) { // If feasible, try a smaller maximum sum right = mid - 1; } else { // If not feasible, we need a larger maximum sum left = mid + 1; } } return (int)left; } private: bool canSplit(const vector<int>& nums, int k, long long maxLimit) { int subarrays = 1; long long currentSum = 0; for (int num : nums) { if (currentSum + num > maxLimit) { // Exceeds limit, must start new subarray subarrays++; currentSum = num; } else { currentSum += num; } } return subarrays <= k; } };
javaclass Solution { public int splitArray(int[] nums, int k) { long left = 0; long right = 0; // Initialize bounds for (int num : nums) { left = Math.max(left, num); right += num; } while (left <= right) { long mid = left + (right - left) / 2; if (canSplit(nums, k, mid)) { // Try to minimize the largest sum right = mid - 1; } else { // Capacity too small, increase it left = mid + 1; } } return (int) left; } private boolean canSplit(int[] nums, int k, long maxLimit) { int subarrays = 1; long currentSum = 0; for (int num : nums) { if (currentSum + num > maxLimit) { subarrays++; currentSum = num; } else { currentSum += num; } } return subarrays <= k; } }
pythonclass Solution: def splitArray(self, nums: List[int], k: int) -> int: def can_split(max_limit): subarrays = 1 current_sum = 0 for num in nums: if current_sum + num > max_limit: subarrays += 1 current_sum = num else: current_sum += num return subarrays <= k left = max(nums) right = sum(nums) while left <= right: mid = left + (right - left) // 2 if can_split(mid): # Try to find a smaller valid maximum sum right = mid - 1 else: # Mid is too small, need larger capacity left = mid + 1 return left
Time Complexity:
nums.nums (minus the max element).canSplit traverses the array in .Space Complexity:
The Binary Search - On Answer pattern is highly reusable. The logic used in LeetCode 410 applies directly to these problems:
k. The condition function checks if Koko can finish within h hours.D days.In all cases, identify the monotonic property: "If X works, does X+1 work?" If yes, apply this pattern.
Why does the naive solution TLE?
Why is the lower bound max(nums) and not 0?
left = 0 or left = min(nums).canSplit function logic breaks or requires extra complexity to handle "impossible to place" numbers.Why is the loop condition subarrays <= k?
subarrays == k.k subarrays with max sum S, we can trivially split it further to get exactly k subarrays without increasing the max sum (by splitting existing subarrays).k partitions.Why use Long/Long Long types?
currentSum or right bound.