面试算法

时间:2012-10-03 00:31:37

标签: algorithm

最近我被问到以下面试问题: 你有两组相同长度的数字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。但看起来他们希望我想出一个公式。你能帮帮我吗?至少我应该寻找什么方向?将不胜感激任何帮助。感谢。

4 个答案:

答案 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 = 1K = 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)次