Given an array of non-negative integers nums
and an integer k
, split the array into k
non-empty continuous subarrays such that the largest sum among these subarrays is minimized.
nums
must be included in exactly one subarray.nums
).k
subarrays.
For example, given nums = [7,2,5,10,8]
and k = 2
, the optimal split is [7,2,5] | [10,8]
, where the largest sum is 18
.
The brute-force way to solve this problem is to try every possible way to split the array into k
continuous subarrays, calculate the largest sum for each split, and find the minimum among them. However, this approach is highly inefficient, especially for larger arrays, because the number of possible splits grows exponentially.
To optimize, we need to realize that the problem is about partitioning the array so that the largest sum is as small as possible. This hints at a binary search on the answer: if we could check whether a particular value is a possible answer (i.e., can we split the array into k
subarrays such that no subarray has a sum greater than this value?), we could efficiently find the minimum possible largest sum.
Thus, the key insight is to use binary search to find the smallest maximum subarray sum, and use a greedy method to check if a given candidate sum is feasible.
Here’s how we solve the problem step by step:
nums
(because every subarray must contain at least one element).nums
(if we don't split at all).k
subarrays so that no subarray sum exceeds mid?nums
, accumulating a running sum. If adding the next number would exceed the current mid-value, start a new subarray.k
, mid is too small.
This approach is efficient because binary search reduces the number of checks, and the greedy check is linear in the size of nums
.
Let's use nums = [7,2,5,10,8]
and k = 2
as our example.
max(nums) = 10
sum(nums) = 32
18
.class Solution:
def splitArray(self, nums, k):
def can_split(max_sum):
count, curr = 1, 0
for num in nums:
if curr + num > max_sum:
count += 1
curr = num
else:
curr += num
return count <= k
left, right = max(nums), sum(nums)
while left < right:
mid = (left + right) // 2
if can_split(mid):
right = mid
else:
left = mid + 1
return left
class Solution {
public:
bool canSplit(vector<int>& nums, int k, int maxSum) {
int count = 1, curr = 0;
for (int num : nums) {
if (curr + num > maxSum) {
count++;
curr = num;
} else {
curr += num;
}
}
return count <= k;
}
int splitArray(vector<int>& nums, int k) {
int left = *max_element(nums.begin(), nums.end());
int right = accumulate(nums.begin(), nums.end(), 0);
while (left < right) {
int mid = left + (right - left) / 2;
if (canSplit(nums, k, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
};
class Solution {
private boolean canSplit(int[] nums, int k, int maxSum) {
int count = 1, curr = 0;
for (int num : nums) {
if (curr + num > maxSum) {
count++;
curr = num;
} else {
curr += num;
}
}
return count <= k;
}
public int splitArray(int[] nums, int k) {
int left = Arrays.stream(nums).max().getAsInt();
int right = Arrays.stream(nums).sum();
while (left < right) {
int mid = left + (right - left) / 2;
if (canSplit(nums, k, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
var splitArray = function(nums, k) {
function canSplit(maxSum) {
let count = 1, curr = 0;
for (let num of nums) {
if (curr + num > maxSum) {
count++;
curr = num;
} else {
curr += num;
}
}
return count <= k;
}
let left = Math.max(...nums);
let right = nums.reduce((a, b) => a + b, 0);
while (left < right) {
let mid = Math.floor((left + right) / 2);
if (canSplit(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
};
k
parts.O(n log S)
, where n
is the length of nums
, and S
is the difference between the sum of nums
and the maximum element. For each binary search step (log S), we do a linear scan (O(n)).O(1)
(excluding input), as we use only a few variables for counting.
The "Split Array Largest Sum" problem challenges us to partition an array into k
continuous subarrays so that the largest sum among them is minimized. While brute-force is infeasible, we achieve an efficient solution by combining binary search (to find the minimal feasible largest sum) with a greedy check (to verify feasibility). This approach is elegant because it leverages the monotonic nature of the problem and transforms a seemingly complex partitioning problem into a series of manageable checks.