leetcode#698
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
注意:
- 1 <= k <= len(nums) <= 16
- 0 < nums[i] < 10000
这个题目,由于k是输入决定的,所以即使想写k重for循环也写不了,并且时间复杂度是O( k n k^n kn),于是尝试递归解决。
思路:
用一个4元状态变量w表示安排第i个数值时每个组内元素和,初始时w为(0,0,0,0)(k=4)。当某个组内元素和大于sum/4时,表示此方案下的递归行不通,返回false;当nums分配完毕时,代表存在分配方案。思路和用递归写01背包问题很像。
代码1:
def search(nums,k,w,target):
if nums==[]:
return True
v=nums.pop()
for i in range(k):
if v+w[i]<=target:
w[i]+=v
if(search(nums,k,w,target)):
return True
w[i]-=v
nums.append(v)
return False
nums=[4, 3, 2, 3, 5, 2, 1]
k=4
nums.sort()
if sum(nums)%k!=0 or nums[-1]>sum(nums)/k:
print(False)
else:
w=[0]*k
target=sum(nums)/k
print(search(nums,k,w,target))
不过这样做跟写k个for循环时间复杂度一样,效率不高。于是看了下leetcode官方解答,在这个递归的基础上进行了改进:
- 首先上面的解法没有注意到每个分组之间的地位相同,逐个进行了尝试;
- 而实际上如果状态变量w(m,0,0,0)下的分支如果不能找到分配方案,那么w(0,m,0,0),w(0,0,m,0)和w(0,0,0,m)不用进行递归就知道不存在分配方案,这样,对于不可能的方案的递归减少了3/4;
- 分析可知,如果将某个值分配给第i组,不能找到解决方案,并且w[i]原来为0,那么不用考虑分配给剩下的几组,直接跳出当前for循环(如果w[i]=0, 0<=i<k-1,那么后面的k-i组不用尝试;如果i=k-1,那么本身就是最后一组)。
因此只需要添加一行break代码
代码2:
def search(nums,k,w,target):
if nums==[]:
return True
v=nums.pop()
for i in range(k):
if v+w[i]<=target:
w[i]+=v
if(search(nums,k,w,target)):
return True
w[i]-=v
if w[i]==0: break
nums.append(v)
return False
nums=[4, 3, 2, 3, 5, 2, 1]
k=4
nums.sort()
if sum(nums)%k!=0 or nums[-1]>sum(nums)/k:
print(False)
else:
w=[0]*k
target=sum(nums)/k
print(search(nums,k,w,target))
很明显这种方法时间复杂度要低,但是具体多少不好分析,官方给出的时间复杂度O(k^(n-k)*k!)。
对于具体分配方案问题,我的想法是递归的时候返回当前物品的状态(分配给1,2,3,…,或者第k组),但这样只能返回一种分配方案,如何返回所有的分配方案目前没有想到好方法,欢迎探讨。