分割数组的最大值
问题描述:
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。 数组长度 n 满足以下条件:
1 ≤ n ≤ 1000,1 ≤ m ≤ min(50, n)
示例:
输入:
nums = [7,2,5,10,8]
m = 2
输出:
18
解释:
一共有四种方法将nums分割为2个子数组。
其中最好的方式是将其分为[7,2,5] 和 [10,8],
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/split-array-largest-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解决方案
贪心+二分
该问题是一道经典的贪心+二分的问题。
不妨设k为子数组的最大和,由题意可知存在如下结论:
若以子数组和最大值为k可以分割出m个子数组,则以k+ 1也一定能分割出m个子数组。
由该结论我们就可以对k从[max(nums), sum(nums)]区间中二分查找出满足条件的k的最小值。上式中下界max(nums)为当前数组的最大值,sum(nums)为当前数组之和。
对于如何判断给定k能否分割出m个子数组,我们可以采用贪心的策略进行分割:从数组第一个元素开始将数组分割为一段一段,使得每一段的长度恰好不大于给定k(即如果再来一个元素的话会现大于k的现象)。判断分割出的子数组是否小于等于m,若其小于等于m,则证明以当前k可以分出m个子数组,其解在[left, k]之间,否则可得当前k有点小不足以分割出m个子数组,解在[m + 1, right]之间。上式中left为当前下界,right为当前上界。
代码如下:
class Solution {
public int splitArray(int[] nums, int m) {
// 定义最大子数组和最大值的上下界,然后再其上下界直接二分+贪心
int left = 0;
int right = 0;
for(int i = 0; i < nums.length; i++){
left = Math.max(left, nums[i]);
right += nums[i];
}
if(m == 1){
return right;
}
if(m == nums.length){
return left;
}
while(left < right){
int mid = (left + right) / 2;
if(canSatis(nums, mid, m)){
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
public boolean canSatis(int[] nums, int maxValue, int m){
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum += nums[i];
if(sum > maxValue){
m--;
sum = nums[i];
}
}
if(sum > 0){
m--;
}
return m >= 0;
}
}
该方案二分的时间复杂度为O(log(N)),判断的时间复杂度为O(N),最终时间复杂度为O(Nlog(N))。额外空间复杂度为O(1)。
动态规划
定义dp[i] [j] 为数组nums从 0 到 j 分割为i个子数组的最小的最大和,dp[m] [N - 1]即为所求。
转移方程:
上式中sum(k:j)表征num从k到j的区间和,求解dp[i] [j]是枚举出所有k,将数组从k到j分为一段,之前的0到k - 1作为i - 1段。dp[i - 1] [k - 1]为前段的最大子数组和,max(…)是为了获得最大子数组和,外面的min(…)是为选出所有分割子数组和最大值最小的那个。
baseline:
若分割为1个子数组,其所有元素之和即为所求。
代码如下:
class Solution {
public int splitArray(int[] nums, int m) {
// dp[i][j] 为nums从0到j范围内分成i个子数组的最小的最大值
int N = nums.length;
int[][] dp = new int[m + 1][nums.length];
int[] sums = new int[N + 1];
for(int i = 1; i <= N; i++){
sums[i] = sums[i - 1] + nums[i - 1];
dp[1][i - 1] = sums[i];
}
for(int i = 2; i <= m; i++){
for(int j = 0; j < N; j++){
dp[i][j] = Integer.MAX_VALUE;
for(int k = j; k > 0; k--){ // 0 ~ k - 1, k ~ j
dp[i][j] = Math.min(dp[i][j], Math.max(dp[i - 1][k - 1], sums[j + 1] - sums[k]));
}
}
}
return dp[m][N - 1];
}
}
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 快速建站“新玩具”—glitch.me
- 踩坑记 | Flutter升级影响了NestedScrollView?
- Android | xml和view的那些事
- Android | 资源冲突覆盖的一些思考
- 如何用脚本自动转化,一个protobuf文件到json格式
- 聊聊dubbo-go的forkingCluster
- 还在用 map[string]interface{} 处理 JSON?告诉你一个更高效的方法——jsonvalue
- 聊聊dubbo-go的failsafeCluster
- 【HDFS】distcp报错Check0sum mismatch
- ffmpeg转换多媒体文件,真香
- 静态库与动态库的那些事
- 云服务器网络延迟与丢包问题定位(mtr工具)
- Spark UDF1 输入复杂结构
- Qt音视频开发6-ffmpeg解码处理
- MySQL 百万级数据量分页查询方法及其优化