详解数组分段和最大值最小问题(最小m段和问题)

数组分段和最大值最小问题(最小m段和问题)

问题描述

给定n个整数组成的序列,现在要求将序列分割为m段,每段子序列中的数在原序列中连续排列。如何分割才能使这m段子序列的和的最大值达到最小?

清洁工:假设有n个房间,清洁每个房间耗时用一个数组表示,10、20、30、40、50、60、70、80、90,安排m个清洁工,将连续的房间分成m份,每部分耗时求和,其最大值为此种分法的总耗时。求最快的耗时是多少。例如3个清洁工的话,10 20 30 40 50 | 60 70 | 80 90,此时是最快的,耗时为170。

装桶:把数据按顺序装入桶中,m即是给定的桶数,问桶的容量至少应该为多少才能恰好把这些数装入m个桶中

思路

先考虑最简单的情况,假设有 1 个数,只有一个桶,m=1:至少需要容量为该数的值;

  • 如果 n 个数,只有一个桶,m=1:至少需要容量为 n 个数之和;
  • 如果 2 个数,两个桶,m=2:至少需要容量为两数之间最大值
  • 如果 t 个数,两个桶,m=2 呢?

我们将 t 个数划分为两份,随机选一个划分位置 k

10, 20, 30, 40, | 50, 60, 70, 80, 90 

就变成两部分: k 个数分到 1 个桶;tk 个数分到一个桶

最少所需容量为 f(t,2)=min{max[f(k,1),f(tk,1)]},1k<t,其中 f(tk,1) 表示后 tk 个数之和,表示为f(tk,1)=f(t1)f(k,1)

  • 现在考虑 n 个数,m 个桶:

我们同样将 n 个数划分为两份,即前 m1 个桶和最后 1 个桶,随机选一个划分位置 k

f(n,m)=min{max[f(k,m1),f(nk,1)]},1k<n

可以使用递归求解了,但是太耗时。我们使用动态规划填表就可以搞定了:

f(i,j)=min{max[f(k,j1),f(i,1)f(k,1)]},1k<n

可以用行表示对应的数字,列表示桶的数目,对1,2,3,4,5,两个桶:

桶1 桶2
1 1 1
2 3 2
3 6 3
4 10 7
5 15 9

coding:

#---python
#动态规划
# dp[i][j] = min(max(dp[k][j-1],dp[i][1]-dp[k][1]))
def dp_minMsum(nums,m):
    n = len(nums)
    dp = [[float('inf')]*(m+1) for _ in range(n+1)]
    
    #初始化
    for i in range(1,n+1):#只有一个桶
        dp[i][1] = sum(nums[:i])
        
    for j in range(1,m+1):#只有一个数
        dp[1][j] = nums[0]
        
    for i in range(2,n+1):
        for j in range(2,m+1):
            for k in range(1,i+1):
                dp[i][j] = min(dp[i][j],max(dp[k][j-1],dp[i][1]-dp[k][1]))
                           
    return dp[-1][-1]

测试下:

>>> nums = [5,3,2,4,1]
>>> m = 2
>>> dp_minMsum(nums,m)
8
>>> nums = [10, 20, 30, 40, 50, 60, 70, 80, 90]   
>>> m=3
>>> dp_minMsum(nums,m)
170

复杂度

时间复杂度:O(MN2)

空间复杂度:O(NM)

二分查找优化

给定当前的桶容量,可以计算出需要的最少桶数:

如果需要的桶数量大于给定的桶数量k,说明桶容量太小了,增大容量;

如果计算需要的桶数量小于等于k,说明桶容量可能大了(也可能正好是要找的最小桶容量)

  • 在给定容量时,求需要桶数
  • 二分搜索桶容量

coding:

#---python
#根据桶容量进行二分查找对应的桶数
def bs_minMsum(nums,m):
    low = max(nums)#容量下界 最大元素
    high = sum(nums)#容量上界 元素和
    while low < high:
        mid = low+(high-low)//2
        buckets = get_buckets(nums,mid)
        if buckets <= m:#该容量下桶数 小于 给定桶数。减小容量
            high = mid
        else:           #桶数 大于 给定桶数。增大容量
            low = mid +1

    return low

def get_buckets(nums,volume):
    buckets = 1
    ans = 0
    for num in nums:
        ans += num
        if ans > volume:
            buckets += 1
            ans = num
    return buckets

复杂度

时间复杂度:O(Nlog(n)),n 为给定数组的元素和

空间复杂度:O(1)

posted @   鱼与鱼  阅读(4455)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示