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)
和上面的做法没有本质区别,只是加上了一个前缀和,优化一些计算