好了,这道题看了2min,一点思路没得,喳喳诚实地打开了百度开始搜索题解。
磨蹭,发呆,喝水,逛blog,约20min后,继续回来写。
这个题网上有两种思路,一种是二分,一种是DP,先来学习我最讨厌的DP。
DP
令dp[i][j]表示把前j个数分为i组的i个和的最大值的最小值(就是题目要求的那个)。
那么子问题表示为min(max(dp[i-1][k],sum(nums[j-k:j]))) ,其中k从i-1取到j-1。
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
n = len(nums)
dp = [[10000000]*(n+1) for _ in range(m+1)]
maxArr=[0]*(n+1)
for i in range(n):
maxArr[i+1] = maxArr[i]+nums[i]
for i in range(m+1):
for j in range(n+1):
dp[i][j] = maxArr[-1]
dp[0][0]=0
for i in range(1,m+1):
for j in range(1,n+1):
for k in range(i-1,j):
value = max(dp[i-1][k],maxArr[j]-maxArr[k])
if value<dp[i][j]:
dp[i][j]=value
return dp[m][n]
尝试了一会,发现自己理解错了,于是可耻地看了大佬的代码,改写成了python。
错误的就不贴了,分析一下这个ac的dp代码,首先速度巨慢,10000ms (orz。。)
首先是定义dp数组,然后是定义一个maxArr数组,表示nums前i个数之和,这个在后面计算sum(nums[i:j])时管用,只要减一下就行了。
然后初始化dp数组,之前自己在这儿出错了,这里将所有位置都初始化为maxArr[-1],也就是nums的和。然后是dp[0][0]=0
开始dp过程,都是从0开始.这里其实可以小改一下,i和j都从1开始,因为分成0组或者0个数分组都是不存在的情况,改动后从10000ms降到了7000ms(???)。然后k的取值很简单,因为这里只考虑分成i-1组,那么至少要有i-1个数,最多j-1个数,不能到j,否则第j组就0个数了,不存在的。
比较的代码:
value = max(dp[i-1][k],maxArr[j]-maxArr[k])
dp[i][j] = min(dp[i][j],value)
这里之前理解也有点小偏差。。应该是把sum(nums[j-k:j])和之前i-1组的最大值比较,找出最大值,然后所有的value中找到最小值,存放在dp[i][j]中。最后返回dp[m][n]就行了。
最后修改后大概5200ms的样子,dp
二分搜索
二分搜索大佬讲解的十分清晰,一看就懂,直接copy思路了。讲的很好:
给定一个值x,判断是否存在一种划分使得每个子区间的和都不超过x,这是容易的。我们可以贪心地遍历一遍数组,不断求和,直到和超过了x值,再新分出一个子区间,最后检查划分出的子区间数是否超过了m。这个检查的时间复杂度为O(n). 然后就可以不断的询问x是否满足上述条件,如果满足说明我们要求的解不超过x,否则说明要求的解大于x,这就构成了一个二分的条件。我们先猜测x属于一个足够大的范围,然后检查中间值是否满足条件,不管结果如何,我们都可以将猜测区间减半。如此不断的缩减区间,就得到了最后的解。因为int的值不超过2^31,所以需要
O(log(2^31))=O(1)次检测,因此算法复杂度是O(n)的。
原文:https://blog.csdn.net/zlasd/article/details/53521265
有了这个思路,二分法的代码很容易写出来,(终于有自己会写的了):
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
l= max(nums)
r = 2**32
mid = 0
def judge(nums,m,mid):
s = nums[0]
cnt=1
for i in nums[1:]:
if s+i>mid:
s=i
cnt+=1
if cnt>m:
return False
else:
s+=i
return True
while l<r:
mid = (l+r)//2
if judge(nums,m,mid):
r = mid
else:
l = mid+1
return l
注意r取大一点,因为有个样例是INT_MAX。。。
用时是56ms,O(n)的算法就是快啊。
这个缩进的格式明天再改吧,今天先到这儿。