410. 分割数组的最大值 Krains 2020-08-29 20:21:39 动态规划二分查找
时间:2022-07-24
本文章向大家介绍410. 分割数组的最大值 Krains 2020-08-29 20:21:39 动态规划二分查找,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
题目链接
二分查找
对答案进行二分,得到mid,如果mid可以将数组切割成m组,并且每组之和小于mid,由于我们要找的是满足要求的最小值,所以可以排除区间(mid, right]
,去[left, mid]
找,如果不满足,那么区间[left, mid]
中任一数必然也不满足条件,应到[mid+1, right]
找。
切割时贪心地去尽可能将让一组的和尽可能大,只有这一组放不下的时候才新开一组。
class Solution {
public int splitArray(int[] nums, int m) {
int right = Integer.MAX_VALUE;
int left = 0;
while(left < right){
int mid = left + (right - left) / 2;
if(canSplit(nums, m, mid)){
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
public boolean canSplit(int[] nums, int m, int sum){
int s = 0;
int count = 1;
for(int num : nums){
// 如果数组中某个数大于sum,无法完成分组
if(num > sum)
return false;
// 如果num能够加入分组,则加入,否者新开一个分组
if(s + num <= sum){
s += num;
}else{
s = num;
count++;
}
}
return count <= m;
}
}
复杂度分析
- 时间复杂度:O(Nlog2I)O(Nlog_2I)O(Nlog2I),I其实等于sum(nums)-min(nums),代码没有体现这一点,主要是由于lc没有给出nums元素的范围。
- 空间复杂度:O(1)O(1)O(1)
动态规划
状态表示
- 集合:使用(i,j)(i, j)(i,j)表示前j个数字分i组的所有分组的集合。
- 属性:一个集合就是一个分组,取各个小组之和有一个最大值,每个集合都有这么一个最大值,f(i,j)f(i,j)f(i,j)表示的就是这些集合中最大值的最小值。
状态计算/集合划分
- 将元素
[0, j)
划分为第i组,f(i,j)=max(f(i−1,0),sum(nums[0,j)))f(i, j) = max(f(i-1, 0), sum(nums[0, j)))f(i,j)=max(f(i−1,0),sum(nums[0,j))) - 将元素
[1, j)
划分为第i组,f(i,j)=max(f(i−1,1),sum(nums[1,j)))f(i, j) = max(f(i-1, 1), sum(nums[1, j)))f(i,j)=max(f(i−1,1),sum(nums[1,j))) - ...
- 将元素
[k, j)
划分为第i组,f(i,j)=max(f(i−1,k),sum(nums[k,j)))f(i, j) = max(f(i-1, k), sum(nums[k, j)))f(i,j)=max(f(i−1,k),sum(nums[k,j)))
取这些集合的最小值就是f(i,j)f(i, j)f(i,j),写出方程
f(i,j)=min(max(f(i−1,k),preSum[j]−preSum[k])),k∈[0,j),j∈[i,n]f(i, j) = min(max(f(i-1,k),preSum[j]-preSum[k])) ,kin[0,j),jin[i,n] f(i,j)=min(max(f(i−1,k),preSum[j]−preSum[k])),k∈[0,j),j∈[i,n]
preSum[j]
是nums[0, j)
的前缀和,n是nums的长度。
边界处理:
初始化二维dp数组为INF,dp[0][0]=0
- 当i=1时,只有将所有的数划分在第i组才有效
- 当i=2时,状态
dp[i-1][j]
当j>=1
是有效的
当不符合划分规则时,dp[i-1][j]=INF,i-1>j
,也就无法选取这些不符合规则的划分。
class Solution {
public int splitArray(int[] nums, int m) {
int n = nums.length;
int[] preSum = new int[n+1];
int[][] dp = new int[m+1][n+1];
for(int i = 1; i <= n; i++){
preSum[i] += preSum[i-1] + nums[i-1];
}
for(int i = 0; i <= m; i++){
Arrays.fill(dp[i], Integer.MAX_VALUE);
}
dp[0][0] = 0;
for(int i = 1; i <= m; i++){
for(int j = i; j <= n; j++){
for(int k = 0; k < j; k++){
dp[i][j] = Math.min(dp[i][j], Math.max(dp[i-1][k], preSum[j] - preSum[k]));
}
}
}
return dp[m][n];
}
}
复杂度分析
- 时间复杂度:O(mn2)O(mn^2)O(mn2)
- 空间复杂度:O(mn)O(mn)O(mn),m是组数,n是nums的长度。
DFS记忆化
用dfs去搜索任何可能划分,使用记忆化剪枝。
class Solution {
// 前缀和数组,因为数组和会超出int范围,因此用long
long[] s;
int n;
public int splitArray(int[] nums, int m) {
n = nums.length;
s = new long[n+1];
for(int i = 1; i <= n; i++){
s[i] = s[i-1] + nums[i-1];
}
return (int)helper(0, 0, m, new Long[n+1][m+1]);
}
// [i, n)是待划分的区域,j表示当前是第几个划分点,j个划分点能够划出j+1个区域
public long helper(int i, int j, int m, Long[][] memo){
if(memo[i][j] != null)
return memo[i][j];
// 当j+1等于m时,划分完毕,直接返回当前区域的和
if(j+1 == m)
return s[n] - s[i];
long min = Integer.MAX_VALUE;
// 穷尽所有划分点
for(int k = i; k < n; k++){
// t是当前划分的组内和的最大值
long t = Math.max(s[k+1]-s[i], helper(k+1, j+1, m, memo));
// min是所有可能划分的组内和的最大值的最小值
min = Math.min(t, min);
}
// 记忆
return memo[i][j] = min;
}
}
- as3: this,stage,root的测试
- 通过iptables限制sftp端口连接数
- Jenkins迁移及日常操作的一点总结
- Nginx反向代理中使用proxy_redirect重定向url
- python try/except/finally
- Jumpserver双机高可用环境部署笔记
- 性能优化
- Nginx基于TCP/UDP端口的四层负载均衡(stream模块)配置梳理
- VB6对滚轮的支持
- 文件实时同步后防篡改的操作记录
- web中的水晶报表 "出现通信错误。将停止打印"
- nginx反向代理中proxy_set_header 运维笔记
- 期待已久的直播能力开放了!年底之前来波大的
- 两个目录中,删除其中一个目录中同名文件的做法
- 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 数组属性和方法
- C# this.invoke()作用 多线程操作UI
- C#3种常见的定时器(多线程)
- C#使用MemoryStream类读写内存
- C# WPF基础之Timer
- Angular 父子Component的数据绑定实现
- C# WPF线程操作
- Angular 界面元素的条件渲染
- mysqlbinlog命令详解 Part 2 - MySQL 事件类型
- Angular list列表的事件响应实现
- Angular list列表绑定的一个例子
- Angular双向绑定的一个例子
- mysqlbinlog命令详解 Part 3 - 查看十六进制格式内容
- MySQL information_schema详解 CHARACTER_SETS 表
- 彻底弄懂 Unicode 编码
- 两种使用代码获得SAP CRM product sales status的办法