说实话,看到这个话题时,我心里就想:“这也太戏剧化了吧,连会议室外面都能听见!”不过仔细一想,这种情况倒也不算完全离谱,尤其是在职场压力大的情况下,谁没在那个不太聪明的领导面前憋过一肚子气呢?
说回来,这个同事真的是“顶得住”啊!不过,大家有没有想过,领导的反应是多么奇葩?你发个表格,大家一看打不开,居然不先检查一下是自己的问题,反而在群里催。
然后同事忍不住回了一句“有催的功夫不如自己点开看看”,本以为这算是个合理的提醒,结果立马引爆了领导的“疯魔模式”。
所以你看要治这种不讲道理的人,有时候要像这位“哥”一样,发个疯,一下子就规规矩矩了。
算法题:分割数组的最大值
又碰到一个经典的算法题:分割数组的最大值。一开始看到这个问题,我脑袋里就浮现出“二分查找+动态规划”的组合拳,不过细细想来,其实这道题目并不需要那么复杂的套路,稍微调整下思路,可能就能搞定。
好了,废话不多说,直接看题:
题目描述:
给定一个数组 nums
和一个整数 m
,将这个数组分成 m
个子数组,使得每个子数组的和的最大值最小。换句话说,我们要尽可能平衡地划分这个数组,使得每个子数组的和尽可能小,但总和又得覆盖整个数组。
目标:
返回划分后的子数组的和的最大值的最小值。
思路分析:
这个题目其实是典型的二分查找+贪心算法的结合。我们可以通过二分查找来试探“可能的最大和”,然后用贪心算法来验证这个最大和能否满足条件。
关键点:
我们要做的就是通过二分查找来确定“分割时每个子数组的最大和”的范围,目标是最小化这个最大和。也就是说,我们需要找到一个最小的“最大和”,使得分割出来的子数组总数不超过 m
。
1. 二分查找的范围
最小值: max(nums)
,即每个子数组的和至少要能容下数组中的最大元素。最大值: sum(nums)
,即整个数组作为一个子数组时的和。
2. 贪心策略
每次尽量让当前的子数组和不超过某个值(我们二分查找的当前猜测值),当超过时,就开始新的一组子数组。
3. 验证
对于每个二分查找的中间值,我们可以用贪心策略来验证这个值是否能分割成不超过 m
个子数组。如果可以,那么我们可以继续尝试更小的值;如果不行,那就得增加这个值。
代码实现:
def splitArray(nums, m):
def canSplit(mid):
# 尝试是否能将数组分割为 <= m 个子数组
current_sum = 0
count = 1 # 至少一个子数组
for num in nums:
if current_sum + num > mid:
count += 1
current_sum = num
if count > m: # 如果分成的子数组数量超过了m个,就返回 False
return False
else:
current_sum += num
return True
left, right = max(nums), sum(nums)
while left < right:
mid = (left + right) // 2
if canSplit(mid):
right = mid # 如果可以分割,说明 mid 可能太大,需要减小
else:
left = mid + 1 # 如果不行,说明 mid 太小,需要增大
return left
代码解析:
**
canSplit(mid)
**:这是一个判断函数,接收一个值mid
,判断是否可以将数组分割成不超过m
个子数组,并且每个子数组的和都不超过mid
。
我们从头开始遍历数组,累加当前子数组的和。如果当前和加上下一个元素超过了 mid
,我们就开始一个新的子数组。如果在遍历过程中,子数组的数量超过了 m
,说明mid
太小,返回False
。
二分查找:通过二分查找来确定 mid
的值的范围。
最小值是数组中最大的一个元素(不能比它小)。 最大值是数组元素的总和(也不能比它大)。 不断调整 left
和right
,直到找到合适的值。
时间复杂度分析:
时间复杂度:O(n * log(sum(nums) - max(nums)))。二分查找的次数是 O(log(sum(nums) - max(nums)))
,每次二分查找都要遍历一次数组nums
,所以时间复杂度是O(n * log(sum(nums) - max(nums)))
。空间复杂度:O(1),除了输入数据外,我们只使用了常数空间。
举个例子:
假设 nums = [7, 2, 5, 10, 8]
,m = 2
。
二分查找的初始范围是 left = 10
(数组中的最大元素)和 right = 32
(数组元素之和)。我们首先尝试 mid = 21
,然后使用 canSplit
检查是否能将数组分割为 2 个子数组,使得每个子数组的和不超过 21。
第一个子数组 [7, 2, 5]
和为 14,没超过 21。第二个子数组 [10, 8]
和为 18,也没超过 21。
由于我们成功将数组分割为 2 个子数组,且每个子数组的和都不超过 21,所以我们继续缩小 mid
,直到找到最小的符合条件的值。
最后总结:
这道题看似复杂,实则用二分查找和贪心策略就能解决,而且效率也非常高。通过二分查找我们能够快速定位可能的最大子数组和,而通过贪心算法来验证每个猜测值是否符合要求,最终找到最优解。
对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》。