Określ brakującą liczbę w strumieniu danych

14

Otrzymujemy strumień n1 par różnych liczb ze zbioru {1,,n} .

Jak mogę ustalić brakującą liczbę za pomocą algorytmu, który odczytuje strumień raz i wykorzystuje pamięć tylko bitów?O(log2n)

Kolejka
źródło

Odpowiedzi:

7

Wiesz i=1ni=n(n+1)2 , a ponieważ można zakodować wbitachO(log(n)),można to zrobić wpamięciO(logn)i na jednej ścieżce (po prostu znajdźS-curS=n(n+1)2O(log(n))O(logn) to brakuje liczba).ScurrentSum

Ale problem ten można rozwiązać w ogólnym przypadku (dla stałej ): mamy k brakujących liczb, znajdź je wszystkie. W tym przypadku zamiast obliczania tylko suma rw I , suma oblicz z j'st potęgi x i dla wszystkich 1 j k (Przypuszczałem X i jest brakujące numery i y i to numery wejściowe):kkyixi1jkxiyi

i=1kxi=S1,i=1kxi2=S2,i=1kxik=Sk (1)

Pamiętaj, że możesz obliczyć po prostu dlatego, S 1 = S - Ď y I , Ś 2 = Σ i 2 - Σ Y 2 i ...S1,...SkS1=SyiS2=i2yi2

Teraz, aby znaleźć brakujące liczby, powinieneś rozwiązać aby znaleźć wszystkie x i .(1)xi

Możesz obliczyć:

, P 2 = x ix j , ..., P k = x iP1=xiP2=xixjPk=xi .(2)

W tym celu należy pamiętać, że , P 2 = S 2 1 - S 2P1=S1 , ...P2=S12S22

Ale to współczynniki P = ( x - x 1 ) ( x - x 2 ) ( x - x k ), ale P można by rozłożyć na czynniki specjalne, dzięki czemu można znaleźć brakujące liczby.PiP=(xx1)(xx2)(xxk)P

To nie są moje myśli; przeczytaj to .

Raphael
źródło
1
Nie rozumiem (2). Może jeśli dodałeś szczegóły sum? Czy brakuje ? Pk
Raphael
@Raphael, to tożsamości Newtona, myślę, że jeśli spojrzysz na moją stronę wiki, na którą się powołujesz, możesz uzyskać pomysł obliczeń, każde P i może być obliczone według poprzednich Ps , S j , pamiętaj prostą formułę: 2 x 1x 2 = ( x 1 + x 2 ) 2 - ( x 2 1 + x 2 2 ) , możesz zastosować podobne podejście do wszystkich mocy. Także jak pisałem P IPiPiPSj2x1x2=(x1+x2)2(x12+x22)Pi jest sigma czegoś, ale nie ma żadnego Σ , ponieważ jest tylko jeden Π . PkΣΠ
Niezależnie od tego, odpowiedzi powinny być samodzielne w rozsądnym stopniu. Dajesz jakieś formuły, więc dlaczego nie uzupełnić ich?
Raphael
11

Z powyższego komentarza:

Before processing the stream, allocate log2n bits, in which you write x:=i=1nbin(i) (bin(i) is the binary representation of i and is pointwise exclusive-or). Naively, this takes O(n) time.

Upon processing the stream, whenever one reads a number j, compute x:=xbin(j). Let k be the single number from {1,...n} that is not included in the stream. After having read the whole stream, we have

x=(i=1nbin(i))(ikbin(i))=bin(k)ik(bin(i)bin(i))=bin(k),
yielding the desired result.

Hence, we used O(logn) space, and have an overall runtime of O(n).

HdM
źródło
3
may I suggest an easy optimization that makes this a true streaming single-pass algorithm: at time step i, xor x with bin(i) and with the input bin(j) that has arrived on the stream. this has the added benefit that you can make it work even if n is not known ahead of time: just start with a single bit allocated for x and "grow" the allocated space as necessary.
Sasho Nikolov
0

HdM's solution works. I coded it in C++ to test it. I can't limit the value to O(log2n) bits, but I'm sure you can easily show how only that number of bits is actually set.

For those that want pseudo code, using a simple fold operation with exclusive or ():

Missing=fold(,{1,,N}InputStream)

Hand-wavey proof: A never requires more bits than its input, so it follows that no intermediate result in the above requires more than the maximum bits of the input (so O(log2n) bits). is commutative, and xx=0, thus if you expand the above and pair off all data present in the stream you'll be left only with a single un-matched value, the missing number.

#include <iostream>
#include <vector>
#include <cstdlib>
#include <algorithm>

using namespace std;

void find_missing( int const * stream, int len );

int main( int argc, char ** argv )
{
    if( argc < 2 )
    {
        cerr << "Syntax: " << argv[0] << " N" << endl;
        return 1;
    }
    int n = atoi( argv[1] );

    //construct sequence
    vector<int> seq;
    for( int i=1; i <= n; ++i )
        seq.push_back( i );

    //remove a number and remember it
    srand( unsigned(time(0)) );
    int remove = (rand() % n) + 1;
    seq.erase( seq.begin() + (remove - 1) );
    cout << "Removed: " << remove << endl;

    //give the stream a random order
    std::random_shuffle( seq.begin(), seq.end() );

    find_missing( &seq[0], int(seq.size()) );
}

//HdM's solution
void find_missing( int const * stream, int len )
{
    //create initial value of n sequence xor'ed (n == len+1)
    int value = 0;
    for( int i=0; i < (len+1); ++i )
        value = value ^ (i+1);

    //xor all items in stream
    for( int i=0; i < len; ++i, ++stream )
        value = value ^ *stream;

    //what's left is the missing number
    cout << "Found: " << value << endl;
}
edA-qa mort-ora-y
źródło
3
Please post readable (pseudo) code of only the algorithm instead (skip main). Also, a correctness proof/argument at some level should be included.
Raphael
4
@edA-qamort-ora-y Your answer assumes that the reader knows C++. To someone who is not familiar with this language, there is nothing to see: both finding the relevant passage and understanding what it's doing are a challenge. Readable pseudocode would make this a better answer. The C++ is not really useful on a computer science site.
Gilles 'SO- stop being evil'
3
If my answer proves not to be useful people don't need to vote for it.
edA-qa mort-ora-y
2
+1 for actually taking the time to write C++ code and test it out. Unfortunately as others pointed out, it's not SO. Still you put effort into this !
Julien Lebot
9
I don't get the point of this answer: you take someone else's solution, which is very simple and obviously very efficient, and "test" it. Why is testing necessary? This is like testing your computer adds numbers correctly. And there is nothing nontrivial abt your code either.
Sasho Nikolov