最近我被问到以下面试问题: 你有两组相同长度的数字N,例如A = [3,5,9]和B = [7,5,1]。接下来,对于范围0..N-1中的每个位置i,您可以选择数字A [i]或B [i],因此最后您将得到另一个长度为N的数组C,其中包含来自A和A的元素B.如果C中所有元素的总和小于或等于K,那么这样的数组是好的。请编写一个算法,通过给定的数组A,B和数字K来计算出良好数组的总数。
我提出的唯一解决方案是动态编程方法,当我们有一个大小为NxK的矩阵时,M [i] [j]表示如果当前总和相等,我们可以为数字X [i]提供多少组合到j。但看起来他们希望我想出一个公式。你能帮帮我吗?至少我应该寻找什么方向?将不胜感激任何帮助。感谢。
答案 0 :(得分:9)
经过一番考虑后,我认为这是一个NP完全问题。考虑:
A = [0, 0, 0, ..., 0]
B = [b1, b2, b3, ..., bn]
请注意,第三组C = ( A[i] or B[i] for i = 0..n )
的每个结构只是A
的某个子集和B
的某个子集的并集。在这种情况下,由于A
的每个子集总和为0
,因此C
的总和与B
的某个子集的总和相同。
现在您的问题“我们可以使用少于C
的总和构建K
多少种方式?”可以重写为“B
总和少于K
的多少子集?”。为K = 1
和K = 0
解决此问题会产生B
A
的解决方案(两个解决方案之间的差异是总和为0的子集数)。 / p>
通过类似的论证,即使在S = [b1-a1, b2-a2, b3-a3, ..., bn-an]
包含非零元素的一般情况下,我们也可以构造一个数组S
,并且问题变为“K - sum(A)
总和的多少子集比{{1}}?“
由于子集和问题是NP完全的,所以这个问题也必须如此。因此,考虑到这一点,我敢说你提出的动态编程解决方案是你能做的最好的,当然也没有神奇的公式。
答案 1 :(得分:0)
所以有2 ^ N个选择,因为在每个点上你要么从A中选择,要么从B中选择。在具体的例子中,你给出N恰好为3的地方有8个。对于讨论,你可以将每组决策定性为位模式。
因此,蛮力方法会尝试每一位模式。
但显而易见的是,如果前几位产生的数字太大,那么每个后续可能的尾位组也会产生一个太大的数字。因此,对它进行建模的一种更好的方法可能就是一棵树,在那里你不必费心走在已经超出极限的肢体上。
您还可以计算从每个位到表末尾可以达到的最大总数。如果在任何时候您的运行总数加上您从此处可以获得的最大值小于K,则可以接受您所在的每个子树,而无需遍历。正如评论中所讨论的那样,每个单一组合都可以接受的案例是这种观察的一个特例。
正如下面的Serge所指出的,一个相关的观察是对我们的最小值,并使用逆向逻辑来取消整个子树而不进行遍历。
潜在的进一步优化在于观察结果,只要我们以相同的方式改变每个,改变A和B的顺序没有效果,因为加法是可交换的。因此,您可以努力确保尽可能快地增加最大值或尽可能慢地增长最小值,以尝试尽可能早地退出遍历。实际上,您可能希望应用一个启发式算法,将绝对最大值和最小值(无论如何都计算)与K进行比较。
在这种情况下,递归实现是最简单的,例如(在C中)
/* assume A, B and N are known globals */
unsigned int numberOfGoodArraysFromBit(
unsigned int bit,
unsigned int runningTotal,
unsigned int limit)
{
// have we ended up in an unacceptable subtree?
if(runningTotal > limit) return 0;
// have we reached the leaf node without at any
// point finding this subtree to be unacceptable?
if(bit >= N) return 1;
// maybe every subtree is acceptable?
if(runningTotal + MAXV[bit] <= limit)
{
return 1 << (N - bit);
}
// maybe no subtrees are acceptable?
if(runningTotal + MINV[bit] > limit)
{
return 0;
}
// if we can't prima facie judge the subtreees,
// we'll need specifically to evaluate them
return
numberOfGoodArraysFromBit(bit+1, runningTotal+A[bit], limit) +
numberOfGoodArraysFromBit(bit+1, runningTotal+B[bit], limit);
}
// work out the minimum and maximum values at each position
for(int i = 0; i < N; i++)
{
MAXV[i] = MAX(A[i], B[i]);
MINV[i] = MIN(A[i], B[i]);
}
// hence work out the cumulative totals from right to left
for(int i = N-2; i >= 0; i--)
{
MAXV[i] += MAXV[i+1];
MINV[i] += MINV[i+1];
}
// to kick it off
printf("Total valid combinations is %u", numberOfGoodArraysFromBit(0, 0, K));
我只是即兴地思考;它可能存在更好的解决方案。
答案 2 :(得分:0)
“请写一个算法计算出好的总数 给定数组A,B和数字K的数组。“
这不是目标吗?
int A[];
int B[];
int N;
int K;
int Solutions = 0;
void FindSolutons(int Depth, int theSumSoFar) {
if (theSumSoFar > K) return;
if (Depth >= N) {
Solutions++;
return;
}
FindSolutions(Depth+1,theSumSoFar+A[Depth]);
FindSolutions(Depth+1,theSumSoFar+B[Depth]);
}
调用FindSolutions
并将两个参数都设置为零。返回时,Solutions
将等于良好数组的数量;
答案 3 :(得分:0)
这就是我试图解决问题的方法 (对不起,如果它很愚蠢)
想到数组
A=[3,5,9,8,2]
B=[7,5,1,8,2]
如果 分子 0..N-1
选择数量
2 ^ N
C1=0,C2=0
for all A[i]=B[i]
{
C1++
C2+=A[i]+B[i]
}
然后创建新的两个数组,如
A1=[3,5,9]
B1=[7,5,1]
现在C2也是10
现在所有选择的数量减少到2 ^(N-C1)
现在计算所有好数字
使用'K'作为K = K-C2
不幸的是 无论你使用什么方法,你都有 计算总和2 ^(N-C1)次