算法题:分割数组的最大值
来聊一个经典的算法题:分割数组的最大值。
题目描述是这样的:
给定一个数组 nums
和一个整数 m
,将数组分成 m
个非空的连续子数组。我们需要找到每个子数组的和的最大值,然后返回这个最大值的最小值。换句话说,我们要尽量平衡这些子数组的和,使得最大和尽可能小。
听起来是不是有点挑战性?其实这个问题关键的地方在于如何有效地在给定的约束下找到最优解。
我们先看个简单例子
假设 nums = [7, 2, 5, 10, 8]
,我们需要把它分成 m = 2
个子数组。目标是将这两个子数组的和最大值最小化。
直观地,我们可以分成两种可能:
子数组1:[7, 2, 5],子数组2:[10, 8],这两个子数组的和分别是 14 和 18,最大值是 18。 子数组1:[7, 2],子数组2:[5, 10, 8],这两个子数组的和分别是 9 和 23,最大值是 23。
所以第一种分割方式的最大值更小,我们的目标就是找到类似这样的最优分割。
思路
首先,我们可以通过二分法来优化问题的解法。我们知道,最大的子数组和至少是数组中最大的单个元素,而最小的子数组和是数组所有元素的和。所以,我们可以将最大值设定在这两个值之间,逐渐逼近最优解。
初始化二分范围:我们设定二分的左右边界为
max(nums)
和sum(nums)
。也就是说,最小可能的最大子数组和是数组中的最大元素,最大可能的最大子数组和是数组元素的总和。二分查找:我们不断地调整二分的中点
mid
,检查是否能够将数组分成m
个子数组,每个子数组的和不超过mid
。如果可以,就说明mid
可以作为一个候选值,接着我们缩小范围来寻找更小的mid
。如果不行,那就意味着mid
太小了,我们需要增大它。划分子数组的判断:每次我们检查当前的
mid
值是否可行时,可以通过遍历数组来模拟子数组的划分。我们从头开始遍历数组,逐个加元素,如果当前和加上这个元素超过了mid
,那么就需要开始一个新的子数组。
代码实现
public class Solution {
public int splitArray(int[] nums, int m) {
// 定义二分的左右边界
int left = 0, right = 0;
for (int num : nums) {
left = Math.max(left, num); // 最小的可能最大值是数组中的最大元素
right += num; // 最大的可能最大值是数组元素的总和
}
// 二分查找
while (left < right) {
int mid = left + (right - left) / 2;
if (canSplit(nums, mid, m)) {
right = mid; // 如果可以分割成m个子数组,则继续缩小上界
} else {
left = mid + 1; // 如果不能,则增大下界
}
}
return left; // 最终的最小的最大值
}
// 判断是否可以将数组分割成m个子数组,且每个子数组的和不超过target
private boolean canSplit(int[] nums, int target, int m) {
int count = 1; // 至少一个子数组
int currentSum = 0;
for (int num : nums) {
currentSum += num;
if (currentSum > target) {
count++; // 如果当前子数组和超过了target,说明需要新建一个子数组
currentSum = num; // 当前元素作为新子数组的第一个元素
if (count > m) {
return false; // 如果分割成了超过m个子数组,返回false
}
}
}
return true; // 如果最终分割成的子数组数量不超过m个,返回true
}
}
代码解释
初始化边界: left
是数组中的最大值,right
是数组元素的总和。二分查找:通过二分查找逐渐缩小 left
和right
的差距,找到最小的mid
,使得数组可以分割成m
个子数组。判断函数: canSplit
函数用来判断当前的mid
是否满足题目要求,即是否能将数组分割成不超过m
个子数组,每个子数组的和不超过mid
。
复杂度分析
时间复杂度:二分查找的时间复杂度是 O(log(sum(nums) - max(nums)))
,每次二分查找需要遍历数组,时间复杂度是O(n)
。因此,总的时间复杂度是O(n * log(sum(nums) - max(nums)))
。空间复杂度:我们只用了常数空间,所以空间复杂度是 O(1)
。
结语
说实话,这种题目刚开始做的时候看起来挺头大的,特别是题目里有分割这种概念,给人一种很复杂的感觉。但通过二分法这种巧妙的方式,就能把问题转化成一个可控的范围,逐渐逼近最优解。
对编程、职场感兴趣的同学,可以链接我,微信:coder301 拉你进入“程序员交流群”。