难度中等168
给你一个整数数组 arr
,请你将该数组分隔为长度 最多 为 k 的一些(连续)子数组。分隔完成后,每个子数组的中的所有值都会变为该子数组中的最大值。
返回将数组分隔变换后能够得到的元素最大和。本题所用到的测试用例会确保答案是一个 32 位整数。
示例 1:
输入:arr = [1,15,7,9,2,5,10], k = 3
输出:84
解释:数组变为 [15,15,15,9,10,10,10]
示例 2:
输入:arr = [1,4,1,5,7,3,6,1,9,9,3], k = 4
输出:83
示例 3:
输入:arr = [1], k = 1
输出:1
提示:
1 <= arr.length <= 500
0 <= arr[i] <= 109
1 <= k <= arr.length
class Solution {
// 由于题目要求的是,子数组元素个数不大于k。那么跟arr[3]在一个分组的情况一共有k种
/**
dp[2]+max(arr[2])*1
dp[1]+max(arr[2], arr[1])*2
dp[0]+max(arr[2], arr[1], arr[0])*3
计算dp[3]只需要计算出比较这k种组合的最大值即可。
*/
public int maxSumAfterPartitioning(int[] arr, int k) {
int n = arr.length;
// dp[i]表示最后一段以arr[i]结尾所能达到的最大值。
// dp[i]=max(dp[i],dp[i-j]+max(arr[i-j:i])*j)
int[] dp = new int[n];
for(int i = 0; i < n; i++){
int max = 0;
for(int j = i-1; j >= -1 && i-j <= k; j--){
max = Math.max(max, arr[j+1]);
dp[i] = Math.max(dp[i], (j >= 0 ? dp[j] : 0) + (i-j) * max);
}
}
return dp[n-1];
}
}
难度困难785
给定一个非负整数数组 nums
和一个整数 m
,你需要将这个数组分成 m
个非空的连续子数组。
设计一个算法使得这 m
个子数组各自和的最大值最小。
示例 1:
输入:nums = [7,2,5,10,8], m = 2
输出:18
解释:
一共有四种方法将 nums 分割为 2 个子数组。
其中最好的方式是将其分为 [7,2,5] 和 [10,8] 。
因为此时这两个子数组各自的和的最大值为18,在所有情况中最小。
示例 2:
输入:nums = [1,2,3,4,5], m = 2
输出:9
示例 3:
输入:nums = [1,4,4], m = 3
输出:4
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 106
1 <= m <= min(50, nums.length)
题解:https://leetcode.cn/problems/split-array-largest-sum/solution/er-fen-cha-zhao-by-liweiwei1419-4/
写在前面的话:
动态规划的写法其实是穷举:按照长度、前缀,枚举最后一个划分,记录每一步结果。细节比较多,需要作图 + 仔细讨论边界情况,并且熟悉二维状态数组、三层 for 循环的写法;
本题的二分查找的思路来源于二分查找的基本思想(应用):查找一个有范围的整数,关键在于利用单调性逼近这个整数。「力扣」上很多问题都基于本题设计,属于「使用二分查找最大值最小化」的一类问题的例题。
class Solution {
public int splitArray(int[] nums, int k) {
int sum = 0, max = 0;
for(int num : nums){
max = Math.max(max, num);
sum += num;
}
int left = max, right = sum;
while(left < right){
int mid = (left + right) >> 1;
if(!check(nums, k, mid)) left = mid + 1;
else right = mid;
}
return right;
}
// 计算 满足不超过「子数组各自的和的最大值」(res) 的分割数
// 看看分割数能否 <= k
public boolean check(int[] nums, int k, int maxIntervalSum){
int splits = 1; // 分割数至少为1
int curIntervalSum = 0;//当前连续区间的和
for(int num : nums){
// 尝试加上当前遍历的这个数,如果加上去超过了「子数组各自的和的最大值」
// 就不加这个数,另起炉灶
if(curIntervalSum + num > maxIntervalSum){
curIntervalSum = 0;
splits++;
}
curIntervalSum += num;
}
return splits <= k;
}
}