我看到这个问题在一个编程面试博客上。
如果按非递减顺序给出
n
个数的配对和,请确定这些单独的数字。 如果总和出现错误,请打印-1
。
示例:
i/p: 4 5 7 10 12 13
o/p: 1 3 4 9
一个提示就够了。设B
是由两两相加得到的列表,使得B[0] <= B[1] <= ... <= B[m-1]
,A
是我们要查找的原始数字列表,其中有n
个数字,且A[0] < A[1] < ... < A[n-1]
,其中m = n(n-1)/2
。
已知A[0]
,计算出A
的值,时间复杂度为多项式级别
从最小元素开始逐步构建A
。假设我们已经知道A[0]
的值。由于B[0]
是B
中最小的元素,因此它只能表示为A[0] + A[1]
。同样地,B[1]
必须等于A[0] + A[2]
。因此,如果我们已知A[0]
,那么我们就可以计算出A[1]
和A[2]
。
然而,在这之后,这种模式就会失效。 B[2]
既可以是A[0] + A[3]
,也可以是A[1] + A[2]
,而没有预先的知识,我们无法确定它是哪一个。然而,如果我们已知A[0]
,那么就可以按照上述方式计算出A[1]
和A[2]
,然后从B
中删除A[1] + A[2]
。接下来最小的元素一定是A[0] + A[3]
,这使得我们可以找到A[3]
。继续这样进行,我们就可以在不回溯的情况下找到A
的所有值。该算法大致如下:
for i from 1 to n-1 {
// REMOVE SEEN SUMS FROM B
for j from 0 to i-2 {
remove A[j]+A[i-1] from B
}
// SOLVE FOR NEXT TERM
A[i] = B[0] - A[0]
}
return A
以下是针对你的例子 B = [4,5,7,10,12,13]
并且我们知道 A[0]=1
的解释:
start
B = [4,5,7,10,12,13]
A[0] = 1
i=1:
B = [4,5,7,10,12,13]
A[1] = 4-1 = 3
i=2:
Remove 1+3 from B
B = [5,7,10,12,13]
A[2] = 5-1 = 4
i=3:
Remove 1+4 and 3+4 from B
B = [10,12,13]
A[3] = 10-1 = 9
end
Remove 1+9 and 3+9 and 4+9 from B
B = []
A = [1,3,4,9]
所以最终的关键在于知道 A[0]
,通过它我们可以计算出其余的A
。
在多项式时间内计算A[0]
现在我们只需要尝试每种可能的A[0]
。由于我们知道 B[0] = A[0] + A[1]
,因此我们知道A[0]
必须是介于0
和 B[0]/2 - 1
之间的整数。我们还知道
B[0] = A[0] + A[1]
B[1] = A[0] + A[2]
此外,存在某个索引 i
,满足 2 <= i <= n-1
,使得
B[i] = A[1] + A[2]
因为唯一可能比 A[1] + A[2]
小的条目是形如 A[0] + A[j]
的条目,而这样的表达式最多只有 n-1
个。因此我们也知道:A[0] = (B[0]+B[1] - B[i])/2
对于一些 2 <= i <= n-1
。这个事实与 A[0]
位于 0
和 B[0]/2-1
之间的事实一起,给出了只有少数几种可能性供测试 A[0]
。
例如,A[0]
有两种可能性:0
或 1
。如果我们使用 A[0]=0
尝试此算法,则会发生以下情况:
start
B = [4,5,7,10,12,13]
A[0] = 0
i=1:
B = [4,5,7,10,12,13]
A[1] = 4-0 = 4
i=2:
Remove 0+4 from B
B = [5,7,10,12,13]
A[2] = 5-0 = 5
i=3:
Remove 0+5 and 4+5 from B
B = !!! PROBLEM, THERE IS NO 9 IN B!
end
B[0]/2-1
可能远大于n
,所以即使在那里进行二分查找,也可能比O(n)
更糟糕。 - PengOne一些提示:
输入的大小为N *(N-1)/ 2,因此您可以推断出输出的大小(即输入中的6个元素对应于输出中的4个元素)
输入的总和是输出总和除以N-1
(即1 + 3 + 4 + 9 =(4 + 5 + 7 + 10 + 12 + 13)/(4-1)
)
最低的输入和最高的输入分别是两个最低输出和两个最高输出的总和(即4 = 1 + 3
和13 = 4 + 9
)
下一个最低的输入(5)与第一个(1)仅相差一个加数,因此您可以通过取差(5-1)来计算其中一个加数。
B[0] = A[0] + A[1]
B[1] = A[0] + A[2]
并且
B[i] = A[1] + A[2]
对于某些 i。
因此,
A[0] = (B[0] + B[1] - B[i]) / 2
我认为Ferdinand Beyer在删除他的答案之前是正确的。重复他的一部分方法:你有四个未知数,a
、b
、c
和d
,其中a ≤ b ≤ c ≤ d
。基于此,可以形成所有和的部分排序:
a + b ≤ a + c
a + b ≤ a + d
a + c ≤ b + c
a + d ≤ b + d
a + d ≤ c + d
b + c ≤ b + d
b + d ≤ c + d
如果这是一个完全有序的集合,那么每个六个值 a + b
, a + c
, a + d
, b + c
, b + d
, 和 c + d
都是已知的。然后可以按照 Ferdinand 的原始计划轻松解决联立方程。
不幸的是,存在一对(a + d
,b + c
)可以以任意顺序排序。但这很容易处理:假设 a + d < b + c
(输入值都是不同的,所以不必担心使用 ≤),并尝试解决联立方程。然后假设 b + c < a + d
并重复。如果两组方程都有解,则原问题有两个答案。如果两组方程都没有解,则输出应为 -1
。否则,您就有了(唯一的)解决方案。
a + d ≤ b + c
,反之亦然。唯一的复杂性是微不足道的:两个解的情况可能会合并为一个解。 - Ted Hopp最近我在查看面试题,并在@PengOne的提示下解决了找到第一个值的问题。
因此,如果有人需要完整的工作解决方案: 它是用PHP编写的:
时间复杂度:O((n *(n-2))+ 3 + n),带有辅助变量。 空间复杂度:几乎与时间复杂度相同。
<?php
function getSublistSize($length)
{
$i = 2;
$n = 0;
while ($i <= $length) {
if (is_int($length / $i)) {
if ($length == $i * ($i + 1) / 2) {
return ($i + 1);
}
}
++$i;
}
return $n;
}
function findSubstractList(array $list)
{
$length = count($list);
$n = getSublistSize($length);
$nth = $n - 1;
$substractList = [];
$substractTotal = array_sum($list) / ($length / 2); // A + B + C + D
/**
* formula : A = (list[0] + list[1] - list[nth -1]) / 2
* list[0] = A + B,
* list[1] = A + C,
* list[nth - 1] = B + C
*
* => ((A + B) + (A + C) - (B + C)) / 2
* => (A + A + (B + C - B - C)) / 2
* => (2A + 0) / 2 => 2A / 2
* => A
*/
$substractList[] = (($list[0] + $list[1]) - $list[$nth]) / 2;
for ($i = 0; $i < $nth; ++$i) {
$substractList[] = ($list[$i] - $substractList[0]);
}
// $substractList[3] = $substractTotal - ($list[$nth - 1] + $substractList[0]);
return $substractList;
}
$list = [5, 8, 14, 28, 40, 11, 17, 31, 43, 20, 34, 46, 40, 52, 66];
print_r(findSubstractList($list));
/**
* P ) [6, 11, 101, 15, 105, 110];
* S ) [1, 5, 10, 100]
*
* P ) [5, 8, 14, 28, 40, 11, 17, 31, 43, 20, 34, 46, 40, 52, 66];
* S ) [1, 4, 7, 13, 27, 39]
*
*/
我不确定最快的算法,但我可以解释这是如何工作的。
输出的第一个数字是第一个输入和第二个输入之间的差异。
5-4=1
所以现在你有了第一个输出数字。
输出的第二个数字是第一个输入数字减去第一个输出数字。
4-1=3
输出的第三个值是第二个输出值减去第一个输入值。
5-1=4