整数の配列が与えられた場合、A1、A2、...、An、負と正、および別の整数Sを含みます。次に、合計が指定された整数Sに最も近い3つの異なる整数を配列内で見つける必要があります。
すべての整数がint32_tの範囲内にあり、合計の計算で算術オーバーフローが発生しないと想定できます。 Sは特別なものではなく、ランダムに選択された数字です。
3つの整数を見つけるためのブルートフォース検索以外の効率的なアルゴリズムはありますか?
3つの整数を見つけるためのブルートフォース検索以外の効率的なアルゴリズムはありますか?
うん;これをO(nで解決できます2)時間!まず、あなたの問題P
が、「ターゲット値」の必要性を排除するわずかに異なる方法で同等に表現できることを考慮してください。
元の問題
P
:配列A
のn
整数とターゲット値S
が与えられた場合、A
合計するとS
?修正された問題
P'
:配列A
のn
整数を与えられた場合、A
から3タプルが存在してゼロになりますか?
P
の各要素からS/3を差し引くことで、このバージョンの問題P'
からA
に移動できることに注意してください。ただし、ターゲット値はもう必要ありません。
明らかに、すべての可能な3タプルを単純にテストする場合、O(n3)-それがブルートフォースのベースラインです。もっと良くすることは可能ですか?タプルをいくぶん賢い方法で選択したらどうなるでしょうか?
最初に、配列の並べ替えに時間を費やします。これには、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.
}
このアルゴリズムは、3つのポインター、i
、j
、およびk
を配列のさまざまなポイントに配置することにより機能します。 i
は最初から始まり、ゆっくりと最後まで動きます。 k
は最後の要素を指します。 j
は、i
が開始した場所を指します。それぞれのインデックスで要素を合計しようとし、次のいずれかが発生するたびに繰り返します。
j
を最後に近づけて、次に大きい番号を選択します。k
を先頭に近づけて、次に小さい番号を選択します。i
ごとに、j
とk
のポインターが徐々に近づいていきます。最終的にそれらは互いに通過し、その時点で、同じ要素を異なる順序で合計するため、そのi
に対して他のことを試みる必要はありません。その後、次のi
を試し、繰り返します。
最終的には、有用な可能性を使い果たすか、解決策を見つけます。これはO(n2)外側のループをO(n)回実行し、内側のループをO(n)回実行するため。各整数をビットベクトルとして表現し、高速フーリエ変換を実行することで、本当におもしろい場合は、これを準二次的に行うことは可能ですが、それはこの答えの範囲外です。
注:これはインタビューの質問であるため、ここで少しごまかしました。このアルゴリズムでは、同じ要素を複数回選択できます。つまり、(-1、-1、2)は(0、0、0)と同様に有効なソリューションです。また、タイトルに記載されているように、exact回答のみを検索し、最も近い回答は検索しません。読者へのエクササイズとして、個別の要素(ただし、非常に単純な変更)と正確な回答(単純な変更でもある)のみで機能させる方法を理解できるようにします。
確かに、これは読みやすく、エラーが発生しにくいため、より優れたソリューションです。唯一の問題は、1つの要素が複数選択されるのを避けるために、数行のコードを追加する必要があることです。
別の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])
John Feminella'sソリューションにはバグがあります。
ラインで
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])
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つの要素の合計が数値と正確に等しいかどうかがわかります。最も近い場合は、最小のデルタ(現在のトリプレットの数の差)を記憶するように変更し、最後に最小のデルタに対応するトリプレットを印刷します。
ソートされた配列があることに注意してください。このソリューションは、合計を検索し、同じ要素を繰り返さないという点のみがジョンのソリューションに似ています。
#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));
}
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;
}
非常に単純なN ^ 2 * logNソリューション:入力配列をソートし、すべてのペアAを通過します私、Aj (N ^ 2時間)、各ペアについて(S-A私 -Aj)は配列内にあります(logN時間)。
別のO(S * N)ソリューションは、古典的な 動的プログラミング アプローチを使用します。
要するに:
2次元配列V [4] [S + 1]を作成します。次のように入力します:
V [0] [0] = 1、V [0] [x] = 0;
V 1 [A私] = 1任意のi、V - 1 [x] = 0他のすべてのx
V [2] [A私 + Aj] = 1、任意のi、j。他のすべてのxに対してV [2] [x] = 0
V [3] [3つの要素の合計] = 1。
それを埋めるために、Aを反復処理します私、各A私 配列を右から左に繰り返します。
これは、次のようにO(n log(n))で効率的に解決できます。任意の3つの数値の合計が指定された数値と等しいかどうかを判断するソリューションを提供しています。
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;
}
}
これは、O [N ^ 2)であるJavaのプログラムです。
import Java.util.Stack;
public class GetTripletPair {
/** Set a value for target sum */
public static final int TARGET_SUM = 32;
private Stack<Integer> stack = new Stack<Integer>();
/** Store the sum of current elements stored in stack */
private int sumInStack = 0;
private int count =0 ;
public void populateSubset(int[] data, int fromIndex, int endIndex) {
/*
* Check if sum of elements stored in Stack is equal to the expected
* target sum.
*
* If so, call print method to print the candidate satisfied result.
*/
if (sumInStack == TARGET_SUM) {
print(stack);
}
for (int currentIndex = fromIndex; currentIndex < endIndex; currentIndex++) {
if (sumInStack + data[currentIndex] <= TARGET_SUM) {
++count;
stack.Push(data[currentIndex]);
sumInStack += data[currentIndex];
/*
* Make the currentIndex +1, and then use recursion to proceed
* further.
*/
populateSubset(data, currentIndex + 1, endIndex);
--count;
sumInStack -= (Integer) stack.pop();
}else{
return;
}
}
}
/**
* Print satisfied result. i.e. 15 = 4+6+5
*/
private void print(Stack<Integer> stack) {
StringBuilder sb = new StringBuilder();
sb.append(TARGET_SUM).append(" = ");
for (Integer i : stack) {
sb.append(i).append("+");
}
System.out.println(sb.deleteCharAt(sb.length() - 1).toString());
}
private static final int[] DATA = {4,13,14,15,17};
public static void main(String[] args) {
GetAllSubsetByStack get = new GetAllSubsetByStack();
get.populateSubset(DATA, 0, DATA.length);
}
}
削減:@John FeminellaのソリューションO(n2)が最もエレガントだと思います。 Tupleを検索するA [n]を減らすことができます。すべての要素がA [0]-A [k]になるようにA [k]を観察することにより、検索配列が巨大でSUMが本当に小さい場合。
A [0]は最小です:-昇順でソートされた配列。
s = 2A [0] + A [k]:sとA []が与えられると、log(n)時間でバイナリ検索を使用してA [k]を見つけることができます。
早期にチェックして失敗する別のソリューション:
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を簡単に使用できます。
この問題は、O(n ^ 2)で2サム問題を少し修正することで解決できます。Aは要素を含むベクトルで、Bは必要な合計です。
int Solution :: threeSumClosest(vector&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;