1. 问题描述:
给定一个非负整数数组 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] <= 10 ^ 6
1 <= m <= min(50, nums.length)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/split-array-largest-sum
2. 思路分析:
对于最大化最小与最小化最大问题一般可以考虑二分查找的方法,但是能够使用二分吗?需要判断先一下,也即能否找到一种方案,使得能够划分为m段,并且每一段的和都是小于等于mid的,答案是可以的,因为数组中的所有元素都是非负的,所以我们可以使用贪心的思想,使得当前这一段的和在满足小于等于mid的前提下包含更多的数字,也即使得段数是更少的(划分段数最少的情况下都可以满足段数是大于等于m的说明可能存在较小的和使得划分的段数是大于等于m的),这样当我们在判断当前的每一段的和是否满足mid的计算需要划分的段数,如果发现段数大于等于m说明答案应该是在左边的,因为最少的划分段数都使得段数是大于等于m的,所以可能存在和更小使得段数大于等于m,更新右边界为mid,否则说明和太小了我们需要使得和大一点,也即答案肯定是在右边,更新左边界为mid + 1即可。
3. 代码如下:
from typing import List
class Solution:
def check(self, nums: List[int], m: int, mid: int):
# count记录划分数组的段数
count = 1
s = 0
for x in nums:
# 不能够将x划分到任意的一段
if x > mid: return False
# 超出了当前的最大和需要另外一段
if s + x > mid:
s = x
count += 1
else:
s += x
# 判断最后一段是否超过了mid
if s > mid: count += 1
# 能够划分的最少段数是否小于等于m
return count <= m
def splitArray(self, nums: List[int], m: int) -> int:
# 因为返回值为int所以右边界为int的最大值即可
l, r = 0, 2147483647
while l < r:
mid = l + r >> 1
if self.check(nums, m, mid):
r = mid
else:
# 和太小了导致段数太多
l = mid + 1
return r