问题:给定数组A,包含n个互不相等的正整数,问有多少种方式从中找出K个数,使得和为arget
例:
A = [1,2,3,4].K = 2,Target = 5
输出:2(1,4;2,3)
问题分析:
考虑最后一步A[n-1]是否选入这K个数
case1:A[n-1]不选入,则需要在前n-1个数里选K个数使得和为Target
case2:A[n-1]选入,则需要在前n-1个数里选K-1个数使得和为Target-A[n-1]
子问题:设f[i][k][s]表示在前i个数里选出k个数使得和为s的方式数,
f[i][k][s] = f[i-1][k][s] + f[i-1][k-1][s-A[i-1]](s>=A[i-1])
= case1 + case2
初始情况:
f[0][0][0] = 1,在前0个数里选出0个数使得和为0的方式数只有一种方法,即什么也不做
f[0][0][s] = 0,(s = 1,...,Target),在前0个数里选出0个数使得和大于0的方式数是0,即做不到
f[0][k][s] = 0,(k>0),前0个数选不出任何数来,因此是0
如果s<A[i-1],则只考虑case1,即不会把A[i-1]选入。
计算顺序:
f[0][0,...,K][0,...,Target]
.
.
.
f[n][0,...,K][0,...,Target]
答案:f[n][K][Target]
时间复杂度O(n*k*Taregt),空间复杂度O(n*k*Target),可以优化到O(k*Target)
代码及注释如下:
def k_sum(A,K,Target):
n = len(A)
#f[i][k][s]表示在前i个数里选出k个数使得和为s的方式数
f = [[[0 for t in range(Target+1)] for k in range(K+1)] for i in range(n+1)]
#初始化f[0][0][0] = 1,f[0][0][s] = 0,(s = 1,...,Target),f[0][k][s] = 0,(k>0)
f[0][0][0] = 1
for i in range(1,n+1):
for k in range(K+1):
for s in range(Target+1):
#f[i][k][s] = f[i-1][k][s] + f[i-1][k-1][s-A[i-1]](s>=A[i-1])
#= case1
f[i][k][s] = f[i-1][k][s]
#case2
if k > 0 and s>=A[i-1] :
f[i][k][s] += f[i-1][k-1][s-A[i-1]]
return f[n][K][Target]
A = [1,2,3,4]
K = 2
Target = 5
print(k_sum(A,K,Target))
#输出:2
空间优化代码如下:
def k_sum(A,K,Target):
n = len(A)
#f[i][k][s]表示在前i个数里选出k个数使得和为s的方式数
f = [[[0 for t in range(Target+1)] for k in range(K+1)] for i in range(2)]
#初始化f[0][0][0] = 1,f[0][0][s] = 0,(s = 1,...,Target),f[0][k][s] = 0,(k>0)
f[0][0][0] = 1
#滚动数组,用new替换i,old替换i-1,每次i循环一次,old和new互换,
#为了保证i循环第一次old = 0,new = 1,故把old的初值设为1,new设为0
old,new = 1,0
for i in range(1,n+1):
old,new = new,old
for k in range(K+1):
for s in range(Target+1):
#f[i][k][s] = f[i-1][k][s] + f[i-1][k-1][s-A[i-1]](s>=A[i-1])
#= case1
f[new][k][s] = f[old][k][s]
#case2
if k > 0 and s>=A[i-1] :
f[new][k][s] += f[old][k-1][s-A[i-1]]
return f[new][K][Target]
A = [1,2,3,4]
K = 2
Target = 5
print(k_sum(A,K,Target))
#输出:2