数组分段和最大值最小问题(最小m段和问题)
问题描述
给定n个整数组成的序列,现在要求将序列分割为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 个桶;\(t-k\) 个数分到一个桶
最少所需容量为 \(f(t,2) = min\{max[f(k,1),f(t-k,1)]\},1\leq k < t\),其中 \(f(t-k,1)\) 表示后 \(t-k\) 个数之和,表示为\(f(t-k,1) = f(t,1)-f(k,1)\)
- 现在考虑 \(n\) 个数,\(m\) 个桶:
我们同样将 \(n\) 个数划分为两份,即前 \(m-1\) 个桶和最后 \(1\) 个桶,随机选一个划分位置 \(k\):
\[f(n,m) = min\{max[f(k,m-1),f(n-k,1)]\},1\leq k < n
\]
\]
可以使用递归求解了,但是太耗时。我们使用动态规划填表就可以搞定了:
\[f(i,j) = min\{max[f(k,j-1),f(i,1)-f(k,1)]\},1\leq k < n
\]
\]
可以用行表示对应的数字,列表示桶的数目,对1,2,3,4,5
,两个桶:
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(MN^2)\)
空间复杂度:\(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(\sum n))\),\(\sum n\) 为给定数组的元素和
空间复杂度:\(O(1)\)