Given an array nums which consists of non-negative integers and an integer m, you can split the array into m non-empty continuous subarrays.
Write an algorithm to minimize the largest sum among these m subarrays.
Example 1:
Input: nums = [7,2,5,10,8], m = 2 Output: 18 Explanation: There are four ways to split nums into two subarrays. The best way is to split it into [7,2,5] and [10,8], where the largest sum among the two subarrays is only 18.
Example 2:
Input: nums = [1,2,3,4,5], m = 2 Output: 9
Example 3:
Input: nums = [1,4,4], m = 3 Output: 4
Constraints:
1 <= nums.length <= 10000 <= nums[i] <= 1061 <= m <= min(50, nums.length)
Companies:
Google, Amazon, Facebook, tiktok, ByteDance
Related Topics:
Array, Binary Search, Dynamic Programming, Greedy
Similar Questions:
- Capacity To Ship Packages Within D Days (Medium)
- Divide Chocolate (Hard)
- Subsequence of Size K With the Largest Even Sum (Medium)
Let dp[i][j+1] be the answer to the subproblem with i subarrays within A[0..j].
Let sum[i][j] be the sum of numbers in A[i..j].
For dp[i][j+1], we can try using A[k..j] as the last subarray. This last subarray has sum sum[k][j], and the A[0..(k-1)] elements have minimum split sum dp[i-1][k]. So, if we try different i - 1 <= k <= j, dp[i][j] is the minimum of max(dp[i-1][k], sum[k][j]).
dp[i][j+1] = min( max(dp[i-1][k], sum[k][j]) | i - 1 <= k <= j )
The trivial case is as follows.
dp[1][j+1] = sum[0][j]
We can compute sum values on the fly instead of precomputing all sum values which takes O(N^2) space.
// OJ: https://leetcode.com/problems/split-array-largest-sum/
// Author: github.com/lzl124631x
// Time: O(N^2 * M)
// Space: O(NM)
class Solution {
public:
int splitArray(vector<int>& A, int m) {
int N = A.size();
vector<vector<int>> dp(m + 1, vector<int>(N + 1, INT_MAX));
dp[1][0] = 0;
for (int i = 0; i < N; ++i) dp[1][i + 1] = dp[1][i] + A[i];
for (int i = 2; i <= m; ++i) {
for (int j = 0; j < N; ++j) {
for (int k = j, sum = 0; k >= i - 1; --k) {
sum += A[k];
dp[i][j + 1] = min(dp[i][j + 1], max(sum, dp[i - 1][k]));
if (sum >= dp[i - 1][k]) break; // If `sum` is already no less than `dp[i-1][k]`, going left won't get smaller split sum. Break directly
}
}
}
return dp[m][N];
}
};Given the dp dependency, we can just use 1D dp array.
// OJ: https://leetcode.com/problems/split-array-largest-sum/
// Author: github.com/lzl124631x
// Time: O(N^2 * M)
// Space: O(N)
class Solution {
public:
int splitArray(vector<int>& A, int m) {
int N = A.size();
vector<int> dp(N + 1, INT_MAX);
dp[0] = 0;
for (int i = 0; i < N; ++i) dp[i + 1] = dp[i] + A[i];
for (int i = 2; i <= m; ++i) {
for (int j = N - 1; j >= 0; --j) {
for (int k = j, sum = 0; k >= i - 1; --k) {
sum += A[k];
dp[j + 1] = min(dp[j + 1], max(sum, dp[k]));
if (sum >= dp[k]) break; // If `sum` is already no less than `dp[i-1][k]`, going left won't get smaller split sum. Break directly
}
}
}
return dp[N];
}
};The answer is between the maximum element and the sum of all the elements.
Let maxSum be the minimum subarray sum allowed. The greater this maxSum is, the more likely we can split A into m parts with maxSum as the maximum subarray sum -- let's call this valid.
There is a breakpoint that if maxSum is less than this value, we have to split A into more than m parts -- let's call this invalid.
We can use binary search to find the minimum valid maxSum.
// OJ: https://leetcode.com/problems/split-array-largest-sum/
// Author: github.com/lzl124631x
// Time: O(N * log(S)) where S is sum of nums.
// Space: O(1)
// Ref: https://discuss.leetcode.com/topic/61324/clear-explanation-8ms-binary-search-java
class Solution {
public:
int splitArray(vector<int>& A, int m) {
long sum = accumulate(begin(A), end(A), 0L);
if (m == 1) return sum;
long L = *max_element(begin(A), end(A)), R = sum, N = A.size();
auto valid = [&](int maxSum) {
int cnt = 1, i = 0 ;
for (int sum = 0; i < N && cnt <= m; ++i) {
sum += A[i];
if (sum > maxSum) {
sum = A[i];
++cnt;
}
}
return i == N && cnt <= m;
};
while (L <= R) {
long M = L + (R - L) / 2;
if (valid(M)) R = M - 1;
else L = M + 1;
}
return L;
}
};