410. 分割数组的最大值
难度: 困难
题目大意:
给定一个非负整数数组
nums 和一个整数k ,你需要将这个数组分成k 个非空的连续子数组。设计一个算法使得这
k 个子数组各自和的最大值最小。提示:
1 <= nums.length <= 1000 0 <= nums[i] <= 10^6 1 <= k <= min(50, nums.length)
示例 1:
输入:nums = [7,2,5,10,8], k = 2 输出:18 解释: 一共有四种方法将 nums 分割为 2 个子数组。 其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。 因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
分析
类似
二分查找 + 贪心
class Solution {
public:
int splitArray(vector<int>& nums, int k) {
int n = nums.size();
function<bool(int)> check = [&](int mid) {
int sum = 0, res = 1;
for (int i = 0; i < nums.size(); i++) {
if (sum + nums[i] > mid) {
res ++;
sum = nums[i];
}
else {
sum += nums[i];
}
}
return res <= k;
};
int l = *max_element(nums.begin(), nums.end());
int r = accumulate(nums.begin(), nums.end(), 0);
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
};
时间复杂度:
O
(
n
l
o
g
(
r
?
l
)
)
O(nlog(r - l))
O(nlog(r?l))
分析
状态定义:我们令
那么状态怎么转移呢?以最后一个不同点为分界点,也就是说,最后一组的起始点,我们可以枚举一下最后一组的起始点,假设当前枚举的是第
朴素版 动态规划
class Solution {
public:
int splitArray(vector<int>& nums, int k) {
int n = nums.size();
// 前i个数字中分成j组各自和的最大值的最小值
vector<vector<int>> f(n + 1, vector<int>(k + 1, 0x3f3f3f3f));
f[0][0] = 0;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= i && j <= k; j ++) {
int sum = 0;
for (int u = i; u ; u --) {
sum += nums[u - 1];
f[i][j] = min(f[i][j], max(f[u - 1][j - 1], sum));
}
}
}
return f[n][k];
}
};
时间复杂度:
O
(
n
2
?
k
)
O(n^2 * k)
O(n2?k)
动态规划 + 前缀和
class Solution {
public:
int splitArray(vector<int>& nums, int k) {
int n = nums.size();
vector<vector<int>> f(n + 1, vector<int>(k + 1, 0x3f3f3f3f));
vector<int> s(n + 1);
// 前缀和
for (int i = 1; i <= n; i ++) s[i] = s[i - 1] + nums[i - 1];
f[0][0] = 0;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= i && j <= k; j ++) {
for (int u = i; u ; u --) {
f[i][j] = min(f[i][j], max(f[u - 1][j - 1], s[i] - s[u - 1]));
}
}
}
return f[n][k];
}
};
时间复杂度:
O
(
n
2
?
k
)
O(n^2 * k)
O(n2?k)
和上面的做法没有本质区别,只是加上了一个前缀和,优化一些计算