How to find if 3 numbers in a set of sizes N exactly sum up to M

I want to know how I can implement a better solution than O (N ^ 3). This is similar to problems with satchels and subsets. In my question, N <= 8000, so I started calculating the sums of pairs of numbers and stored them in an array. Then I would have a binary search in a sorted set for each value (M-sum [i]), but there is a problem, how will I keep track of indexes summed to the sum of [i]. I know that I can declare extra space, but my Sums array already has a size of 64 million, and therefore I could not complete my O (N ^ 2) solution. Please advice if I can do some optimization or if I need a completely different technique.

+3
source share
6 answers

, .

1) ,

, , . , , , , : " ? , ( ), ( , ), .

O (N ** 2)! : O (N).

2) , : -

- ( -) , - , O (1), ( , ).

, ( - ), - .

"log N" .

, :

  • -: - , ,
  • : for i in [0..N-1]; for j in [i+1..N-1]
  • , K = M - set[i] - set[j] -, , k = table[K] k != i k != j (i,j,k) .

, , , .

+4

O (n ^ 2), O (1) *, 3 (O (n) , , ).

.

., () , . O (n) .

, , , , , .

, n O (n) O (n ^ 2).


* , , O (1). , O (n ^ 2), . Heapsort O (n log n) O (1) .

+3

" " , , , . .

O (N ^ 2), .

- , , , 3 , "" , 2 , .

3 - , M 3, M/3 3 .

, MAX/8, MAX . -, : O (1) .

+1

...

#include <iostream>
#include <set>
#include <algorithm> 
using namespace std;

int main(void)
{
  set<long long> keys;

  // By default this set is sorted
  set<short> N;
  N.insert(4);
  N.insert(8);
  N.insert(19);
  N.insert(5);
  N.insert(12);
  N.insert(35);
  N.insert(6);
  N.insert(1);

  typedef set<short>::iterator iterator;

  const short M = 18;

  for(iterator i(N.begin()); i != N.end() && *i < M; ++i)
  {
    short d1 = M - *i; // subtract the value at this location
    // if there is more to "consume"
    if (d1 > 0)
    {
      // ignore below i as we will have already scanned it...
      for(iterator j(i); j != N.end() && *j < M; ++j)
      {
        short d2 = d1 - *j; // again "consume" as much as we can
        // now the remainder must eixst in our set N
        if (N.find(d2) != N.end())
        {
          // means that the three numbers we've found, *i (from first loop), *j (from second loop) and d2 exist in our set of N
          // now to generate the unique combination, we need to generate some form of key for our keys set
          // here we take advantage of the fact that all the numbers fit into a short, we can construct such a key with a long long (8 bytes)

          // the 8 byte key is made up of 2 bytes for i, 2 bytes for j and 2 bytes for d2
          // and is formed in sorted order

          long long key = *i; // first index is easy
          // second index slightly trickier, if it less than j, then this short must be "after" i
          if (*i < *j)
            key = (key << 16) | *j; 
          else
            key |= (static_cast<int>(*j) << 16); // else it before i

          // now the key is either: i | j, or j | i (where i & j are two bytes each, and the key is currently 4 bytes)

          // third index is a bugger, we have to scan the key in two byte chunks to insert our third short
          if ((key & 0xFFFF) < d2)
            key = (key << 16) | d2;  // simple, it the largest of the three
          else if (((key >> 16) & 0xFFFF) < d2)
            key = (((key << 16) | (key & 0xFFFF)) & 0xFFFF0000FFFFLL) | (d2 << 16); // its less than j but greater i
          else
            key |= (static_cast<long long>(d2) << 32); // it less than i

          // Now if this unique key already exists in the hash, this won't insert an entry for it
          keys.insert(key);
        }
        // else don't care...
      }
    }
  }
  // tells us how many unique combinations there are  
  cout << "size: " << keys.size() << endl;
  // prints out the 6 bytes for representing the three numbers
  for(set<long long>::iterator it (keys.begin()), end(keys.end()); it != end; ++it)
     cout << hex << *it << endl;

  return 0;
}

, : :

start: 19
size: 4
10005000c
400060008
500050008
600060006

, "" - ( ), 0x0001, 0x0005, 0x000C ( 1, 5, 12 = 18) ..

, , , .

My Big O notation ( ), , , - O(N) O(NlogN) , log N , std::set::find() - , , , O(N) - , - , ...

0

@Matthieu M. @Chris Hopman, ( ) , O (n log n + log (nk)! + k) O (log (nk)) (). O (n log n) . Python, Python .

import bisect

def binsearch(r, q, i, j): # O(log (j-i))
    return bisect.bisect_left(q, r, i, j)

def binfind(q, m, i, j):    
    while i + 1 < j:
        r = m - (q[i] + q[j])
        if r < q[i]:
            j -= 1
        elif r > q[j]:
            i += 1
        else:
            k = binsearch(r, q, i + 1, j - 1)  # O(log (j-i))
            if not (i < k < j):
                return None
            elif q[k] == r:
                return (i, k, j)
            else:
                return (
                    binfind(q, m, i + 1, j)
                    or
                    binfind(q, m, i, j - 1)
                    )

def find_sumof3(q, m):
    return binfind(sorted(q), m, 0, len(q) - 1)
0

. ++. , , , , 3 0. , .

#include <iostream>
using namespace std;

void merge(int originalArray[], int low, int high, int sizeOfOriginalArray){
    //    Step 4: Merge sorted halves into an auxiliary array
    int aux[sizeOfOriginalArray];
    int auxArrayIndex, left, right, mid;

    auxArrayIndex = low;
    mid = (low + high)/2;
    right = mid + 1;
    left = low;

    //    choose the smaller of the two values "pointed to" by left, right
    //    copy that value into auxArray[auxArrayIndex]
    //    increment either left or right as appropriate
    //    increment auxArrayIndex
    while ((left <= mid) && (right <= high)) {
        if (originalArray[left] <= originalArray[right]) {
            aux[auxArrayIndex] = originalArray[left];
            left++;
            auxArrayIndex++;
        }else{
            aux[auxArrayIndex] = originalArray[right];
            right++;
            auxArrayIndex++;
        }
    }

    //    here when one of the two sorted halves has "run out" of values, but
    //    there are still some in the other half; copy all the remaining values
    //    to auxArray
    //    Note: only 1 of the next 2 loops will actually execute
    while (left <= mid) {
        aux[auxArrayIndex] = originalArray[left];
        left++;
        auxArrayIndex++;
    }

    while (right <= high) {
        aux[auxArrayIndex] = originalArray[right];
        right++;
        auxArrayIndex++;
    }

    //    all values are in auxArray; copy them back into originalArray
    int index = low;
    while (index <= high) {
        originalArray[index] = aux[index];
        index++;
    }
}

void mergeSortArray(int originalArray[], int low, int high){
    int sizeOfOriginalArray = high + 1;
    //    base case
    if (low >= high) {
        return;
    }

    //    Step 1: Find the middle of the array (conceptually, divide it in half)
    int mid = (low + high)/2;

    //    Steps 2 and 3: Recursively sort the 2 halves of origianlArray and then merge those
    mergeSortArray(originalArray, low, mid);
    mergeSortArray(originalArray, mid + 1, high);
    merge(originalArray, low, high, sizeOfOriginalArray);
}

//O(n^2) solution without hash tables
//Basically using a sorted array, for each number in an array, you use two pointers, one starting from the number and one starting from the end of the array, check if the sum of the three elements pointed to by the pointers (and the current number) is >, < or == to the targetSum, and advance the pointers accordingly or return true if the targetSum is found.

bool is3SumPossible(int originalArray[], int targetSum, int sizeOfOriginalArray){
    int high = sizeOfOriginalArray - 1;
    mergeSortArray(originalArray, 0, high);

    int temp;

    for (int k = 0; k < sizeOfOriginalArray; k++) {
        for (int i = k, j = sizeOfOriginalArray-1; i <= j; ) {
            temp = originalArray[k] + originalArray[i] + originalArray[j];
            if (temp == targetSum) {
                return true;
            }else if (temp < targetSum){
                i++;
            }else if (temp > targetSum){
                j--;
            }
        }
    }
    return false;
}

int main()
{
    int arr[] = {2, -5, 10, 9, 8, 7, 3};
    int size = sizeof(arr)/sizeof(int);
    int targetSum = 5;

    //3Sum possible?
    bool ans = is3SumPossible(arr, targetSum, size); //size of the array passed as a function parameter because the array itself is passed as a pointer. Hence, it is cummbersome to calculate the size of the array inside is3SumPossible()

    if (ans) {
        cout<<"Possible";
    }else{
        cout<<"Not possible";
    }

    return 0;
}
0

Source: https://habr.com/ru/post/1786200/


All Articles