给定一个整数数组,A 1 ,A 2 ,...,A n ,包括底片和正片,以及另一个整数S.现在我们需要在数组中找到三个不同的整数,其总和最接近给定的整数S.如果存在多个解,则其中任何一个都可以。
您可以假设所有整数都在int32_t范围内,并且计算总和时不会发生算术溢出。 S并不是特别的,而是随机选择的数字。
除了暴力搜索之外还有其他有效算法可以找到三个整数吗?
答案 0 :(得分:182)
除了暴力搜索之外还有其他有效算法可以找到三个整数吗?
是的;我们可以在O(n 2 )时间内解决这个问题!首先,请考虑您的问题P
可以采用略微不同的方式进行等效措辞,从而消除对“目标价值”的需求:
原始问题
P
:鉴于A
个整数的数组n
和目标值S
,是否存在3元组从A
汇总到S
?修改后的问题
P'
:鉴于A
个整数的数组n
,是否存在来自A
的3元组总和为零?
请注意,您可以通过从P'
中的每个元素中减去S / 3来从P
的问题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.
}
此算法通过在数组中的各个点放置三个指针i
,j
和k
来工作。 i
从一开始就开始,慢慢地一路走到尽头。 k
指向最后一个元素。 j
指向i
开始的位置。我们迭代地尝试在各自的索引处对元素求和,并且每次发生以下情况之一:
j
移近末尾以选择下一个最大数字。k
移近开头,选择下一个最小的数字。对于每个i
,j
和k
的指针会逐渐相互靠近。最终它们会相互传递,此时我们不需要为i
尝试任何其他内容,因为我们将按照不同的顺序对相同的元素进行求和。在那之后,我们尝试下一个i
并重复。
最终,我们将耗尽有用的可能性,或者我们将找到解决方案。你可以看到这是O(n 2 ),因为我们执行外循环O(n)次,我们执行内循环O(n)次。通过将每个整数表示为位向量并执行快速傅里叶变换,如果你真的很喜欢,可以做这个次要的二次方,但这超出了这个答案的范围。
注意:因为这是一个面试问题,我在这里做了一点作弊:这个算法允许多次选择相同的元素。也就是说,(-1,-1,2)将是一个有效的解决方案,因为(0,0,0)。它还只找到完全答案,而不是最接近的答案,正如标题所提到的那样。作为对读者的练习,我将让你弄清楚如何使它只使用不同的元素(但这是一个非常简单的变化)和确切的答案(这也是一个简单的变化)。
答案 1 :(得分:28)
当然这是一个更好的解决方案,因为它更容易阅读,因此不容易出错。唯一的问题是,我们需要添加几行代码以避免多个选择一个元素。
另一个O(n ^ 2)解决方案(使用散列集)。
// 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])
答案 2 :(得分: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.
}
}
这会发现3个元素的总和是否与您的数字完全相等。如果你想要最接近,你可以修改它以记住最小的三角形(当前三元组的数量之间的差异),最后打印对应于最小三角形的三元组。
答案 3 :(得分:6)
John Feminella的解决方案有一个错误。
在
行if (A[i] + A[j] + A[k] == 0) return (A[i], A[j], A[k])
我们需要检查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])
答案 4 :(得分:5)
请注意,我们有一个已排序的数组。此解决方案与John的解决方案类似,只是它查找总和并且不重复相同的元素。
#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));
}
答案 5 :(得分:3)
这是C ++代码:
bool FindSumZero(int a[], int n, int& x, int& y, int& z)
{
if (n < 3)
return false;
sort(a, a+n);
for (int i = 0; i < n-2; ++i)
{
int j = i+1;
int k = n-1;
while (k >= j)
{
int s = a[i]+a[j]+a[k];
if (s == 0 && i != j && j != k && k != i)
{
x = a[i], y = a[j], z = a[k];
return true;
}
if (s > 0)
--k;
else
++j;
}
}
return false;
}
答案 6 :(得分:2)
非常简单的N ^ 2 * logN解决方案:对输入数组进行排序,然后遍历所有对A i ,A j (N ^ 2时间),并且每一对检查(S - A i - A j )是否在数组中(logN时间)。
另一个O(S * N)解决方案使用经典的dynamic programming方法。
简而言之:
创建一个二维数组V [4] [S + 1]。以这样的方式填写:
V [0] [0] = 1,V [0] [x] = 0;
对于任何i,V 1 [A i ] = 1,对于所有其他x,V 1 [x] = 0
对于任何i,j,V [2] [A i + A j ] = 1。所有其他x
的V [2] [x] = 0V [3] [任何3个元素的总和] = 1。
要填充它,迭代A i ,每个A i 从右到左遍历数组。
答案 7 :(得分:1)
这是java中的程序,它是O(N ^ 2)
<ion-view cache-view="false" view-title="My Title!">
答案 8 :(得分:1)
这可以在O(n log(n))中有效地解决如下。 我正在给出解决方案,告诉我们任何三个数字的总和是否等于给定的数字。
import java.util.*;
public class MainClass {
public static void main(String[] args) {
int[] a = {-1, 0, 1, 2, 3, 5, -4, 6};
System.out.println(((Object) isThreeSumEqualsTarget(a, 11)).toString());
}
public static boolean isThreeSumEqualsTarget(int[] array, int targetNumber) {
//O(n log (n))
Arrays.sort(array);
System.out.println(Arrays.toString(array));
int leftIndex = 0;
int rightIndex = array.length - 1;
//O(n)
while (leftIndex + 1 < rightIndex - 1) {
//take sum of two corners
int sum = array[leftIndex] + array[rightIndex];
//find if the number matches exactly. Or get the closest match.
//here i am not storing closest matches. You can do it for yourself.
//O(log (n)) complexity
int binarySearchClosestIndex = binarySearch(leftIndex + 1, rightIndex - 1, targetNumber - sum, array);
//if exact match is found, we already got the answer
if (-1 == binarySearchClosestIndex) {
System.out.println(("combo is " + array[leftIndex] + ", " + array[rightIndex] + ", " + (targetNumber - sum)));
return true;
}
//if exact match is not found, we have to decide which pointer, left or right to move inwards
//we are here means , either we are on left end or on right end
else {
//we ended up searching towards start of array,i.e. we need a lesser sum , lets move inwards from right
//we need to have a lower sum, lets decrease right index
if (binarySearchClosestIndex == leftIndex + 1) {
rightIndex--;
} else if (binarySearchClosestIndex == rightIndex - 1) {
//we need to have a higher sum, lets decrease right index
leftIndex++;
}
}
}
return false;
}
public static int binarySearch(int start, int end, int elem, int[] array) {
int mid = 0;
while (start <= end) {
mid = (start + end) >>> 1;
if (elem < array[mid]) {
end = mid - 1;
} else if (elem > array[mid]) {
start = mid + 1;
} else {
//exact match case
//Suits more for this particular case to return -1
return -1;
}
}
return mid;
}
}
答案 9 :(得分:0)
减少:我认为@John Feminella解决方案O(n2)是最优雅的。我们仍然可以减少搜索元组的A [n]。通过观察A [k]使得所有元素都在A [0] - A [k]中,当我们的搜索数组很大而SUM(s)真的很小时。
A [0]是最小值: - 升序排序数组。
s = 2A [0] + A [k]:给定s和A [],我们可以在log(n)时间内使用二分搜索找到A [k]。
答案 10 :(得分:0)
另一种早期检查和失败的解决方案:
public boolean solution(int[] input) {
int length = input.length;
if (length < 3) {
return false;
}
// x + y + z = 0 => -z = x + y
final Set<Integer> z = new HashSet<>(length);
int zeroCounter = 0, sum; // if they're more than 3 zeros we're done
for (int element : input) {
if (element < 0) {
z.add(element);
}
if (element == 0) {
++zeroCounter;
if (zeroCounter >= 3) {
return true;
}
}
}
if (z.isEmpty() || z.size() == length || (z.size() + zeroCounter == length)) {
return false;
} else {
for (int x = 0; x < length; ++x) {
for (int y = x + 1; y < length; ++y) {
sum = input[x] + input[y]; // will use it as inverse addition
if (sum < 0) {
continue;
}
if (z.contains(sum * -1)) {
return true;
}
}
}
}
return false;
}
我在这里添加了一些单元测试:GivenArrayReturnTrueIfThreeElementsSumZeroTest。
如果集合占用太多空间,我可以轻松使用将使用O(n / w)space的java.util.BitSet。
答案 11 :(得分:0)
通过稍微修改扩展2和问题,可以在O(n ^ 2)中解决问题.A是包含元素的向量,B是所需的和。
int Solution :: threeSumClosest(vector&amp; A,int B){
sort(A.begin(),A.end());
int k=0,i,j,closest,val;int diff=INT_MAX;
while(k<A.size()-2)
{
i=k+1;
j=A.size()-1;
while(i<j)
{
val=A[i]+A[j]+A[k];
if(val==B) return B;
if(abs(B-val)<diff)
{
diff=abs(B-val);
closest=val;
}
if(B>val)
++i;
if(B<val)
--j;
}
++k;
}
return closest;
答案 12 :(得分:0)
编程以获取这三个元素。我刚刚对数组/列表进行了排序,然后根据每个三元组更新了minCloseness
。
public int[] threeSumClosest(ArrayList<Integer> A, int B) {
Collections.sort(A);
int ansSum = 0;
int ans[] = new int[3];
int minCloseness = Integer.MAX_VALUE;
for (int i = 0; i < A.size()-2; i++){
int j = i+1;
int k = A.size()-1;
while (j < k){
int sum = A.get(i) + A.get(j) + A.get(k);
if (sum < B){
j++;
}else{
k--;
}
if (minCloseness > Math.abs(sum - B)){
minCloseness = Math.abs(sum - B);
ans[0] = A.get(i); ans[1] = A.get(j); ans[2] = A.get(k);
}
}
}
return ans;
}
答案 13 :(得分:0)
这是Python3代码
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
result = set()
nums.sort()
L = len(nums)
for i in range(L):
if i > 0 and nums[i] == nums[i-1]:
continue
for j in range(i+1,L):
if j > i + 1 and nums[j] == nums[j-1]:
continue
l = j+1
r = L -1
while l <= r:
sum = nums[i] + nums[j] + nums[l]
result.add(sum)
l = l + 1
while l<=r and nums[l] == nums[l-1]:
l = l + 1
result = list(result)
min = result[0]
for i in range(1,len(result)):
if abs(target - result[i]) < abs(target - min):
min = result[i]
return min
答案 14 :(得分:-1)
我在n ^ 3中这样做,我的伪代码在下面;
//创建一个hashMap,键为Integer,值为ArrayList //使用for循环遍历列表,对于列表中的每个值,再次从下一个值开始迭代;
for (int i=0; i<=arr.length-1 ; i++){
for (int j=i+1; j<=arr.length-1;j++){
//如果arr [i]和arr [j]的总和小于期望的总和那么就有可能找到第三个数字,所以做另一个循环
if (arr[i]+arr[j] < sum){
for (int k= j+1; k<=arr.length-1;k++)
//在这种情况下,我们现在正在寻找第三个值;如果总和 arr [i]和arr [j]和arr [k]是所需的总和,然后通过将arr [i]作为密钥然后将arr [j]和arr [k]添加到ArrayList中来将这些添加到HashMap中该键的值
if (arr[i]+arr[j]+arr[k] == sum){
map.put(arr[i],new ArrayList<Integer>());
map.get(arr[i]).add(arr[j]);
map.get(arr[i]).add(arr[k]);}
在此之后,您现在有一个字典,其中包含表示三个值的所有条目添加到所需的总和。使用HashMap函数提取所有这些条目。这非常有效。