在下面的答案中,我将考虑数组
A
,其中所有的值都是严格正数,并且数组中的值是唯一的。首先,我想从相对明显的事实开始。
- 如果数组
A
有2个数字,最小的数字集合就是两个(即集合A
本身)
- 如果数组
A
有3个数字,最小的数字集合将为2,当且仅当最小数字的和等于第三个数字时,否则将为3(即集合A
本身)
在注意到这一点之后,我想从相反的方向来看问题,这将为我们解决问题提供洞察力。给定一个包含
n
个数字的集合,可以组合出总共
2**n - 1
个不同的数字,因为集合中的每个数字都可以单独存在或不参与求和。
示例
给定一个包含三个数字
{a,b,c}
的集合,总共可以生成
2**3 - 1 = 7
个数字:
[a, b, c, a+b, a+c, b+c, a+b+c]
因此,根据A
的长度(表示为m
),我们可以推导出最小集合数的下界:
下界 = ceil(log2(m+1))
这意味着长度为4至7的A
的下界为3。长度为8至15的下界为4,依此类推。
解决方案建议
这将提出一个解决方案建议,它可能有些缺陷,但涵盖了我能想到的大部分内容。
由于我们现在有了一个下界,如果我们找到一个长度为下界的集合来解决我们的问题,那么它肯定是最优解,结果应该等于下界。
我建议采用以下解决方案:
对于ss在范围3到m之间的每个值
1.1. 获取2和ss-1之间的所有组合,检查每个组合的总和是否在数组中。如果是,则从数组中删除该总和
1.2. 从A中的每个剩余值中减去所有其余剩余值,创建一个对称矩形矩阵,其中对角线为0
1.3. 对于形成的矩阵中两个正值的每种可能组合,检查以下内容:
1.3.1. 组合的总和是否在A中
1.3.2. A中的数字是否可以分解为两个数字,这些数字可以与A的其他成员相加。如果是,则从A中删除三个数字,并添加两个分解后的数字
Python实现如下:
from itertools import combinations
import numpy as np
def get_subset_count(A):
m = len(A)
if m == 2:
return 2
elif m == 3:
return 3 if A[0] + A[1] == A[2] else 2
else:
lower_bound = int(np.ceil(np.log2(m + 1)))
for ss in range(lower_bound, m):
A_temp = A.copy()
removed = set()
sub_values = []
for kk in range(2, ss+1):
for comb in combinations(A[:ss+1, kk):
if sum(comb) in A_temp:
A_temp.remove(sum(comb))
removed.add(sum(comb))
if len(A_temp) <= ss: return ss
deduction = np.array(A_temp)[:, None] - np.array(A_temp)
for comb in combinations(deduction[deduction > 0].tolist(), 2):
if sum(comb) in A_temp:
A_temp2 = A_temp.copy()
A_temp2.remove(sum(comb))
comb0_logic = [comb[0] + a in A_temp2 for a in A_temp2]
comb1_logic = [comb[1] + a in A_temp2 for a in A_temp2]
if any(comb0_logic) and any(comb1_logic):
A_temp.remove(sum(comb))
removed.add(sum(comb))
A_temp.remove(sum(comb[0] + np.array(A_temp2)[comb0_logic]))
removed.add(sum(comb[0] + np.array(A_temp2)[comb0_logic]))
sub_values = [comb[0]] + sub_values
if comb[0] + np.array(A_temp2)[comb0_logic] != comb[1] + np.array(A_temp2)[comb1_logic]:
A_temp.remove(sum(comb[1] + np.array(A_temp2)[comb1_logic]))
removed.add(sum(comb[1] + np.array(A_temp2)[comb1_logic]))
sub_values = [comb[1]] + sub_values
if len(A_temp) + len(sub_values) <= ss: return ss
return len(A)
当使用以下内容运行此方法时:
A = [4,5,7,16]
print(get_subset_count(A))
A = [9,11,12,16]
print(get_subset_count(A))
A = [4,5,7,9,11,12,16]
print(get_subset_count(A))
A = [4,5,7,9,11,12,17]
print(get_subset_count(A))
A = [18,20,37,42,50]
print(get_subset_count(A))
A = [18,20,37,20,42,50,55]
print(get_subset_count(A))
A = [18,37,20,42,50,54]
print(get_subset_count(A))
A = [1,2,3,4,5,6,7,8,9,10]
print(get_subset_count(A))
关于运行时,在我看来,由于它具有组合的性质,这是NP问题。
失败点
这种方法仍然无法发现一些样本,我将用一个例子来解释:
对于A = [4,13,21,37,45,62]
存在一个下限集合,即[4,8,13,37]
。分别将集合成员表示为[a,b,c,d]
,我们可以描述A
如下:
A = [a, c, c+b, d, d+b, a+b+c+d]
我的建议方法将会发现 62 = 4+21+37
并将其删除,然后它将不会发现 45 - 37 = 21 - 13 = 8
改进的建议
在寻找这些情况的解决方案后,我想到了以下方法:
def get_subset_count(A):
m = len(A)
if m == 2:
return 2
elif m == 3:
return 3 if A[0] + A[1] == A[2] else 2
else:
lower_bound = int(np.ceil(np.log2(m + 1)))
for ss in range(lower_bound, m):
A_temp = A.copy()
removed = set()
sub_values = []
for kk in range(2, ss+1):
for comb in combinations(A[:ss+1], kk):
if sum(comb) in A_temp:
A_temp.remove(sum(comb))
removed.add(sum(comb))
if len(A_temp) <= ss: return ss
deduction = np.array(A_temp)[:, None] - np.array(A_temp)
for comb in combinations(deduction[deduction > 0].tolist(), 2):
if sum(comb) in A_temp:
A_temp2 = A_temp.copy()
A_temp2.remove(sum(comb))
comb0_logic = [comb[0] + a in A_temp2 for a in A_temp2]
comb0_logic_sub = [comb[0] + a in A_temp2 for a in sub_values]
comb1_logic = [comb[1] + a in A_temp2 for a in A_temp2]
comb1_logic_sub = [comb[1] + a in A_temp2 for a in sub_values]
if any(comb0_logic) and any(comb1_logic):
A_temp.remove(sum(comb))
removed.add(sum(comb))
A_temp.remove(sum(comb[0] + np.array(A_temp2)[comb0_logic]))
removed.add(sum(comb[0] + np.array(A_temp2)[comb0_logic]))
if comb[0] not in sub_values: sub_values.append(comb[0])
if comb[0] + np.array(A_temp2)[comb0_logic] != comb[1] + np.array(A_temp2)[comb1_logic]:
A_temp.remove(sum(comb[1] + np.array(A_temp2)[comb1_logic]))
removed.add(sum(comb[1] + np.array(A_temp2)[comb1_logic]))
if comb[1] not in sub_values: sub_values.append(comb[1])
elif (comb[0] in sub_values) or (comb[1] in sub_values):
if any(comb0_logic_sub):
A_temp.remove(sum(comb[0] + np.array(sub_values)[comb0_logic_sub]))
removed.add(sum(comb[0] + np.array(sub_values)[comb0_logic_sub]))
if comb[0] not in sub_values: sub_values.append(comb[0])
if any(comb1_logic_sub):
A_temp.remove(sum(comb[1] + np.array(sub_values)[comb1_logic_sub]))
removed.add(sum(comb[1] + np.array(sub_values)[comb1_logic_sub]))
if comb[1] not in sub_values: sub_values.append(comb[1])
elif comb[0] == comb[1]:
A_temp2 = A_temp.copy()
comb0_logic = [comb[0] + a in A_temp2 for a in A_temp2]
if any(comb0_logic):
removed_numbers = comb[0] + np.array(A_temp2)[comb0_logic]
for removed_number in removed_numbers:
A_temp.remove(removed_number)
removed.add(removed_number)
if comb[0] not in sub_values: sub_values.append(comb[0])
elif comb[0] in sub_values and comb[1] in sub_values and sum(comb) in A_temp:
A_temp.remove(sum(comb))
removed.add(sum(comb))
for kk in range(2, ss+1):
for comb in combinations(sub_values, kk):
if sum(comb) in A_temp:
A_temp.remove(sum(comb))
if len(A_temp) + len(sub_values) <= ss: return ss
return len(A)