查找数组中的三个元素,其总和最接近给定数字

Zel*_*luX 155 arrays algorithm

给定一个整数数组,A 1,A 2,...,A n,包括负数和正数,以及另一个整数S.现在我们需要在数组中找到三个不同的整数,其总和最接近给定的整数S如果存在多个解决方案,则其中任何一个都可以.

您可以假设所有整数都在int32_t范围内,并且计算总和时不会发生算术溢出.S没什么特别的,只是随机挑选的数字.

有没有比强力搜索更有效的算法来找到三个整数?

Joh*_*lla 186

有没有比强力搜索更有效的算法来找到三个整数?

是的; 我们可以在O(n 2)时间内解决这个问题!首先,考虑一下你的问题P可以用一种略微不同的方式来表达,从而消除了对"目标值"的需求:

原来的问题P:给定一个阵列An整数和目标值S,就存在着从一个3元组A求和以S

改性问题P':给定一个阵列An整数,不存在从3元组A求和为零?

请注意,您可以从这个版本的问题,去P'P通过从每个元素减去你的S/3 A,但现在你不需要目标值了.

显然,如果我们只是测试所有可能的3元组,我们就可以解决O(n 3)中的问题 - 这就是蛮力基线.有可能做得更好吗?如果我们以更聪明的方式选择元组怎么办?

首先,我们花费一些时间对数组进行排序,这使我们的初始惩罚为O(n log n).现在我们执行这个算法:

for (i in 1..n-2) {
  j = i+1  // Start right after i.
  k = n    // Start at the end of the array.

  while (k >= j) {
    // We got a match! All done.
    if (A[i] + A[j] + A[k] == 0) return (A[i], A[j], A[k])

    // We didn't match. Let's try to get a little closer:
    //   If the sum was too big, decrement k.
    //   If the sum was too small, increment j.
    (A[i] + A[j] + A[k] > 0) ? k-- : j++
  }
  // When the while-loop finishes, j and k have passed each other and there's
  // no more useful combinations that we can try with this i.
}
Run Code Online (Sandbox Code Playgroud)

该算法的工作原理是将三分,i,j,并k在不同的点在数组中.i从一开始就开始慢慢地运行到最后.k指向最后一个元素.j指向i已经开始的地方.我们迭代地尝试在各自的索引处对元素求和,并且每次发生以下情况之一:

  • 总和是完全正确的!我们找到了答案.
  • 总和太小了.移动j更接近最终选择未来最大数量.
  • 总和太大了.移动k接近开始选择下一个最小的数.

对于每一个i,指针jk将逐渐变得彼此靠近.最终它们会相互传递,此时我们不需要为此尝试任何其他因素i,因为我们只是以不同的顺序对相同的元素进行求和.在那之后,我们尝试下一个i并重复.

最终,我们将耗尽有用的可能性,或者我们将找到解决方案.你可以看到这是O(n 2),因为我们执行外循环O(n)次,我们执行内循环O(n)次.如果您真的很喜欢,可以通过将每个整数表示为位向量并执行快速傅立叶变换来实现这种子二次方,但这超出了本答案的范围.


注意:因为这是一个面试问题,我在这里做了一点作弊:这个算法允许多次选择相同的元素.也就是说,(-1,-1,2)将是一个有效的解决方案,因为(0,0,0).它也只能找到确切的答案,而不是最接近的答案,正如标题所提到的那样.作为对读者的练习,我将让你弄清楚如何使它只使用不同的元素(但这是一个非常简单的变化)和确切的答案(这也是一个简单的变化).

  • 如果我们不修改问题陈述,我们将搜索总和为ai + S的aj和ak. (11认同)
  • 似乎算法只能找到*等于*到S的3元组,而不是*最接近*的S元组. (8认同)
  • ZelluX:正如我在笔记中所提到的,我不想放弃太多,因为这是一个面试问题.希望你能看到如何修改它,以便它能得到你最接近的答案.(提示:到目前为止,一种方法是跟踪最接近的答案,如果找到更好的答案,则覆盖它.) (7认同)
  • @ZelluX:它与合并排序的工作方式类似(这就是它首次点击的方式).内循环试图做的是证明A [j]或A [k]不能成为*任何*令人满意的解决方案的一部分.任何一点的问题是:"是否有任何对j'> = j和k'<= k使得A [j] + A [k] = S - A [i]?" 看看当前的一对(i,j),有三种可能性:总和就是爆炸(停止 - 我们赢了!),它太低了,或者它太高了.如果它太低,则总和A [j] + A [k']也必须太低而不能*每*k'<= k,因为在每个这样的和中,第一项(A [j])将是相同... (2认同)

Cem*_*Cem 28

当然这是一个更好的解决方案,因为它更容易阅读,因此不容易出错.唯一的问题是,我们需要添加几行代码以避免多个选择一个元素.

另一个O(n ^ 2)解决方案(使用hashset).

// K is the sum that we are looking for
for i 1..n
    int s1 = K - A[i]
    for j 1..i
        int s2 = s1 - A[j]
        if (set.contains(s2))
            print the numbers
    set.add(A[i])
Run Code Online (Sandbox Code Playgroud)

  • 下行是O(N)存储,而不是就地存储. (8认同)
  • 使用哈希集不严格O(n ^ 2),因为哈希集在极少数情况下可以退化,导致最多线性查找时间. (6认同)

Ram*_*bo7 7

John Feminella的解决方案有一个错误.

在线

if (A[i] + A[j] + A[k] == 0) return (A[i], A[j], A[k])
Run Code Online (Sandbox Code Playgroud)

我们需要检查i,j,k是否都是不同的.否则,如果我的目标元素是6,如果我的输入数组包含{3,2,1,7,9,0,-4,6}.如果我打印出总和为6的元组,那么我也会得到0,0,6输出.为避免这种情况,我们需要以这种方式修改条件.

if ((A[i] + A[j] + A[k] == 0) && (i!=j) && (i!=k) && (j!=k)) return (A[i], A[j], A[k])
Run Code Online (Sandbox Code Playgroud)

  • 实际上,我永远不会是j因为你总是在j = i + 1开始它.你应该检查的唯一真实条件是j == k.但是,通过将while循环设置为j <k,您已经解决了问题而没有冗长的if语句,因为k总是大于j而j总是大于i. (3认同)
  • John Feminella解决方案只是提出解决问题的算法,他还指出他的解决方案不适用于不同的数字条件,你必须修改上面的代码,他留给读者一点点. (2认同)
  • 这似乎不是对这个问题的回答,而是对John Feminella的答案的评论. (2认同)

cod*_*ict 6

这样的事情怎么样,这是O(n ^ 2)

for(each ele in the sorted array)
{
    ele = arr[i] - YOUR_NUMBER;
    let front be the pointer to the front of the array;
    let rear be the pointer to the rear element of the array.;

    // till front is not greater than rear.                    
    while(front <= rear)
    {
        if(*front + *rear == ele)
        {
            print "Found triplet "<<*front<<","<<*rear<<","<<ele<<endl;
            break;
        }
        else
        {
            // sum is > ele, so we need to decrease the sum by decrementing rear pointer.
            if((*front + *rear) > ele)
                decrement rear pointer.
            // sum is < ele, so we need to increase the sum by incrementing the front pointer.
            else
                increment front pointer.
        }
    }
Run Code Online (Sandbox Code Playgroud)

这会发现3个元素的总和是否与您的数字完全相等.如果你想要最接近,你可以修改它以记住最小的三角形(当前三元组的数量之间的差异),并在最后打印对应于最小三角形的三元组.


小智 5

请注意,我们有一个排序数组.该解决方案是类似于约翰的解决方案只知道它看起来的总和,并且不重复相同的元素.

#include <stdio.h>;

int checkForSum (int arr[], int len, int sum) { //arr is sorted
    int i;
    for (i = 0; i < len ; i++) {
        int left = i + 1;
        int right = len - 1;
        while (right > left) {
            printf ("values are %d %d %d\n", arr[i], arr[left], arr[right]);
            if (arr[right] + arr[left] + arr[i] - sum == 0) {
                printf ("final values are %d %d %d\n", arr[i], arr[left], arr[right]);
                return 1;
            }
            if (arr[right] + arr[left] + arr[i] - sum > 0)
                right--;
            else
                left++;
        }
    }
    return -1;
}
int main (int argc, char **argv) {
    int arr[] = {-99, -45, -6, -5, 0, 9, 12, 16, 21, 29};
    int sum = 4;
    printf ("check for sum %d in arr is %d\n", sum, checkForSum(arr, 10, sum));
}
Run Code Online (Sandbox Code Playgroud)