web-dev-qa-db-fra.com

La question de l'entrevue s'est compliquée: numéro donné 1..100, trouver le (s) numéro (s) manquant (s) exactement k sont manquants

J'ai eu une expérience d'entrevue d'emploi intéressante il y a quelque temps. La question a commencé vraiment facile:

Q1 : Nous avons un sac contenant des nombres 1, 2, 3,…, 100. Chaque numéro apparaît exactement une fois, il y a donc 100 nombres. Maintenant, un numéro est choisi au hasard dans le sac. Trouvez le numéro manquant.

Bien sûr, j'ai déjà entendu cette question d'entrevue auparavant, alors j'ai très rapidement répondu comme suit:

A1 : Bien, la somme des nombres 1 + 2 + 3 + … + N est (N+1)(N/2) (voir Wikipedia: somme de séries arithmétiques) ). Pour N = 100, la somme est 5050.

Ainsi, si tous les nombres sont présents dans le sac, la somme sera exactement 5050. Puisqu'il manque un nombre, la somme sera inférieure à cela et la différence est ce nombre. Nous pouvons donc trouver ce nombre manquant dans l'espace O(N) et O(1).

À ce stade, je pensais avoir bien réussi, mais tout à coup, la question a pris une tournure inattendue:

Q2 : C'est exact, mais maintenant, comment procéderiez-vous si DEUX numéros sont manquants ?

Je n'avais jamais vu/entendu/envisagé cette variation auparavant, alors j'ai paniqué et je ne pouvais pas répondre à la question. L’intervieweur a insisté pour connaître mon processus de pensée. J’ai donc mentionné que nous pourrions peut-être obtenir plus d’informations en comparant avec le produit attendu, ou peut-être en faisant une deuxième passe après avoir recueilli des informations de la première passe, etc. dans le noir plutôt que d'avoir réellement un chemin clair vers la solution.

L'intervieweur a essayé de m'encourager en disant qu'une deuxième équation est en effet un moyen de résoudre le problème. À ce stade, j'étais un peu contrarié (de ne pas connaître la réponse à l'avance) et je lui ai demandé s'il s'agissait d'une technique de programmation générale (lire: "utile") ou s'il s'agissait simplement d'une réponse astucieuse.

La réponse de l'intervieweur m'a surpris: vous pouvez généraliser la technique pour trouver 3 nombres manquants. En fait, vous pouvez le généraliser pour trouver k nombres manquants.

Qk : Si exactement k nombres manquaient dans le sac, comment le trouveriez-vous efficacement?

C'était il y a quelques mois et je ne pouvais toujours pas comprendre en quoi consiste cette technique. Il y a évidemment une limite inférieure dans le temps Ω(N) puisque nous devons scanner tous les nombres au moins une fois, mais l'intervieweur a insisté pour que le TIME et le ESPACE la complexité de la technique de résolution (moins l'analyse O(N) time) est définie dans k pas N .

Donc la question est simple:

  • Comment résoudriez-vous Q2 ?
  • Comment résoudriez-vous Q3 ?
  • Comment résoudriez-vous Qk ?

Des clarifications

  • Généralement, il existe N des nombres de 1 .. N, pas seulement 1..100.
  • Je ne cherche pas la solution évidente basée sur un ensemble, par exemple. en utilisant un jeu de bits , codant la présence/l'absence de chaque nombre par la valeur d'un bit désigné, donc en utilisant O(N) bits dans un espace supplémentaire. Nous ne pouvons nous permettre aucun espace supplémentaire proportionnel à N.
  • Je ne cherche pas non plus l'approche évidente du tri en premier. Ceci et l’approche basée sur les ensembles méritent d’être mentionnés dans un entretien (ils sont faciles à mettre en œuvre et, en fonction de N, ils peuvent être très pratiques). Je cherche la solution du Saint-Graal (qui peut ou non être pratique à mettre en œuvre, mais qui présente néanmoins les caractéristiques asymptotiques souhaitées).

Encore une fois, bien sûr, vous devez analyser l'entrée dans O(N), mais vous ne pouvez capturer qu'une petite quantité d'informations (définie en termes de k pas N), et doit ensuite trouver les nombres manquants k .

1098
polygenelubricants

Voici un résumé de Dimitris Andreolien .

Rappelez-vous la somme des i puissances, où i = 1,2, .., k. Cela réduit le problème à la résolution du système d'équations

une1 + un2 + ... + ak = b1

une12 + un22 + ... + ak2 = b2

...

une1k + un2k + ... + akk = bk

Utilisation de identités de Newton , sachant bje permet de calculer

c1 = un1 + un2 + ... ak

c2 = un1une2 + un1une3 + ... + ak-1unek

...

ck = un1une2 ... unek

Si vous développez le polynôme (x-a1) ... (x-ak) les coefficients seront exactement c1, ..., ck - voir formules de Viète . Puisque chaque facteur polynomial est unique (l’anneau des polynômes est un domaine euclidien ), cela signifie unje sont uniquement déterminés, jusqu'à la permutation.

Ceci met fin à la preuve que le souvenir des pouvoirs est suffisant pour récupérer les chiffres. Pour k constant, c’est une bonne approche.

Cependant, lorsque k varie, l’approche directe de l’informatique c1, ..., ck est excessivement cher, car p. ex. ck est le produit de tous les nombres manquants, magnitude n!/(n-k) !. Pour surmonter cela, effectuez calculs dans Zq field , où q est un nombre premier tel que n <= q <2n - il existe par postulat de Bertrand . La preuve n'a pas besoin d'être modifiée, car les formules sont toujours valables et la factorisation des polynômes est encore unique. Vous avez également besoin d'un algorithme de factorisation sur des corps finis, par exemple celui de Berlekamp ou Cantor-Zassenhaus .

Pseudocode de haut niveau pour la constante k:

  • Calculer les puissances de nombres donnés
  • Soustrayez-vous pour obtenir la somme de la ieme puissance de nombres inconnus. Appelez les sommes bje.
  • Utiliser les identités de Newton pour calculer les coefficients à partir de bje; appelez-les cje. Fondamentalement, c1 = b1; c2 = (c1b1 - b2)/2; voir Wikipedia pour les formules exactes
  • Facteur le polynôme xk-c1xk-1 + ... + ck.
  • Les racines du polynôme sont les nombres nécessaires a1, ..., unek.

Pour faire varier k, trouvez un nombre premier n <= q <2n à l'aide de, par exemple. Miller-Rabin, et effectuez les étapes avec tous les nombres réduits modulo q.

EDIT: La version précédente de cette réponse a déclaré qu'au lieu de Zq, où q est premier, il est possible d’utiliser un corps fini de caractéristique 2 (q = 2 ^ (log n)). Ce n'est pas le cas, car les formules de Newton nécessitent une division par des nombres allant jusqu'à k.

570
sdcvvc

Vous le trouverez en lisant les quelques pages de Muthukrishnan - Algorithmes de flux de données: casse-tête 1: recherche des nombres manquants. Il montre exactement la généralisation que vous recherchez . C'est probablement ce que votre intervieweur a lu et pourquoi il a posé ces questions.

Maintenant, si seulement les gens commençaient à supprimer les réponses qui sont comprises ou remplacées par le traitement de Muthukrishnan, cela faciliterait la recherche de ce texte. :)


Voir aussi sdcvvc réponse directement liée , qui inclut également un pseudocode (bravo! Inutile de lire ces formulations mathématiques difficiles :)) (merci, excellent travail!).

236
Dimitris Andreou

Nous pouvons résoudre Q2 en additionnant les nombres eux-mêmes et les carrés des nombres.

On peut alors réduire le problème à

k1 + k2 = x
k1^2 + k2^2 = y

x et y sont la mesure dans laquelle les sommes sont inférieures aux valeurs attendues.

La substitution nous donne:

(x-k2)^2 + k2^2 = y

Que nous pouvons alors résoudre pour déterminer nos nombres manquants.

169
Anon.

Comme @j_random_hacker l'a fait remarquer, cela ressemble beaucoup à Trouver des doublons dans O(n) time et O(1) space , et une adaptation de ma réponse fonctionne ici aussi.

En supposant que le "sac" soit représenté par un tableau basé sur 1 A[] de taille N - k, nous pouvons résoudre Qk en O(N) temps et O(k) espace supplémentaire.

Premièrement, nous étendons notre tableau A[] de k éléments, de sorte qu’il a maintenant la taille N. C'est la O(k) espace supplémentaire. Nous exécutons ensuite l'algorithme de pseudo-code suivant:

for i := n - k + 1 to n
    A[i] := A[1]
end for

for i := 1 to n - k
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 1 to n
    if A[i] != i then 
        print i
    end if
end for

La première boucle initialise les entrées supplémentaires k de la même manière que la première entrée du tableau (il s’agit simplement d’une valeur pratique que nous savons déjà présente dans le tableau - après cette étape, toutes les entrées manquantes dans le tableau). tableau initial de taille N-k sont toujours manquants dans le tableau étendu).

La deuxième boucle permute le tableau étendu de sorte que si l'élément x est présent au moins une fois, l'une de ces entrées sera alors à la position A[x].

Notez que, bien qu’elle ait une boucle imbriquée, elle fonctionne toujours dans O(N) time - un échange n’est effectué que s’il existe un i tel que A[i] != i, et chaque échange définit au moins un élément tel que ce A[i] == i, où ce n'était pas vrai avant. Cela signifie que le nombre total de swaps (et donc le nombre total d'exécutions du corps de la boucle while) est au plus N-1.

La troisième boucle affiche les index du tableau i qui ne sont pas occupés par la valeur i - cela signifie que i doit avoir été manquant.

132
caf

J'ai demandé à un enfant de 4 ans de résoudre ce problème. Il tria les chiffres puis compta. Cela a une exigence d'espace de O (sol de la cuisine), et cela fonctionne aussi facilement, même si beaucoup de balles manquent.

122
Colonel Panic

Je ne suis pas sûr qu'il s'agisse de la solution la plus efficace, mais je bouclerais toutes les entrées et utiliserais un jeu de bits pour rappeler quels nombres sont définis, puis pour tester 0 bit.

J'aime les solutions simples - et je crois même que cela pourrait être plus rapide que de calculer la somme, ou la somme des carrés, etc.

34
Chris Lercher

Je n'ai pas vérifié les calculs, mais je soupçonne que le calcul de Σ(n^2) dans le même passage que nous calculons Σ(n) fournirait suffisamment d'informations pour obtenir deux nombres manquants, ainsi que Σ(n^3) s'il y a sont trois, et ainsi de suite.

32
AakashM

Le problème avec les solutions basées sur des sommes de nombres est qu'elles ne prennent pas en compte le coût de stockage et de travail avec des nombres avec des exposants importants ... en pratique, pour fonctionner avec un très grand n, une bibliothèque de grands nombres serait utilisée . Nous pouvons analyser l'utilisation de l'espace pour ces algorithmes.

Nous pouvons analyser la complexité temporelle et spatiale des algorithmes de sdcvvc et de Dimitris Andreou.

Espace de rangement:

l_j = ceil (log_2 (sum_{i=1}^n i^j))
l_j > log_2 n^j  (assuming n >= 0, k >= 0)
l_j > j log_2 n \in \Omega(j log n)

l_j < log_2 ((sum_{i=1}^n i)^j) + 1
l_j < j log_2 (n) + j log_2 (n + 1) - j log_2 (2) + 1
l_j < j log_2 n + j + c \in O(j log n)`

Donc, l_j \in \Theta(j log n)

Stockage total utilisé: \sum_{j=1}^k l_j \in \Theta(k^2 log n)

Espace utilisé: en supposant que le calcul de a^j prend ceil(log_2 j) temps, temps total:

t = k ceil(\sum_i=1^n log_2 (i)) = k ceil(log_2 (\prod_i=1^n (i)))
t > k log_2 (n^n + O(n^(n-1)))
t > k log_2 (n^n) = kn log_2 (n)  \in \Omega(kn log n)
t < k log_2 (\prod_i=1^n i^i) + 1
t < kn log_2 (n) + 1 \in O(kn log n)

Temps total utilisé: \Theta(kn log n)

Si ce temps et cet espace sont satisfaisants, vous pouvez utiliser un algorithme récursif simple. Soit b! I la ième entrée dans le sac, n le nombre de nombres avant enlèvement et k le nombre d’enlèvements. Dans la syntaxe Haskell ...

let
  -- O(1)
  isInRange low high v = (v >= low) && (v <= high)
  -- O(n - k)
  countInRange low high = sum $ map (fromEnum . isInRange low high . (!)b) [1..(n-k)]
  findMissing l low high krange
    -- O(1) if there is nothing to find.
    | krange=0 = l
    -- O(1) if there is only one possibility.
    | low=high = low:l
    -- Otherwise total of O(knlog(n)) time
    | otherwise =
       let
         mid = (low + high) `div` 2
         klow = countInRange low mid
         khigh = krange - klow
       in
         findMissing (findMissing low mid klow) (mid + 1) high khigh
in
  findMising 1 (n - k) k

Stockage utilisé: O(k) pour la liste, O(log(n)) pour la pile: O(k + log(n)) Cet algorithme est plus intuitif, a la même complexité temporelle et utilise moins d'espace.

15
a1kmm

Attends une minute. Comme la question est posée, il y a 100 numéros dans le sac. Peu importe la taille de k, le problème peut être résolu en un temps constant, car vous pouvez utiliser un ensemble et supprimer des nombres de l'ensemble dans au plus 100 k itérations d'une boucle. 100 est constant. L'ensemble des nombres restants est votre réponse.

Si nous généralisons la solution aux nombres de 1 à N, rien ne change sauf que N n'est pas une constante, nous sommes donc en O (N - k) = O(N) temps. Par exemple, si nous utilisons un jeu de bits, nous définissons les bits à 1 dans le temps O(N), itérons à travers les nombres, définissons les bits à 0 au fur et à mesure (O (Nk) = O(N)) et alors nous avons la réponse.

Il me semble que l'intervieweur vous demandait comment imprimer le contenu du jeu final dans O(k) time plutôt que O(N) temps. Clairement, avec un bit défini, vous devez parcourir tous les N bits pour déterminer si vous devez imprimer le nombre ou non. Toutefois, si vous modifiez la manière dont l'ensemble est mis en œuvre, vous pouvez imprimer les nombres en k itérations. Ceci est fait en plaçant les nombres dans un objet à stocker dans un ensemble de hachage et une liste doublement liée. Lorsque vous supprimez un objet de l'ensemble de hachage, vous le supprimez également de la liste. Les réponses seront laissées dans la liste qui est maintenant de longueur k.

12
JeremyP

Pour résoudre la question de 2 (et 3) nombres manquants, vous pouvez modifier quickselect , qui s'exécute en moyenne dans O(n) et utilise une mémoire constante si le partitionnement est effectué sur place.

  1. Répartissez l'ensemble par rapport à un pivot aléatoire p en partitions l, qui contiennent des nombres plus petits que le pivot, et r, qui contiennent des nombres plus grands que le pivot.

  2. Déterminez les partitions sur lesquelles se trouvent les 2 nombres manquants en comparant la valeur de pivot à la taille de chaque partition (p - 1 - count(l) = count of missing numbers in l et n - count(r) - p = count of missing numbers in r)

  3. a) Si un numéro manque à chaque partition, utilisez la méthode des différences de sommes pour trouver chaque nombre manquant.

    (1 + 2 + ... + (p-1)) - sum(l) = missing #1 et ((p+1) + (p+2) ... + n) - sum(r) = missing #2

    b) Si une partition manque les deux numéros et que la partition est vide, les numéros manquants sont soit (p-1,p-2) ou (p+1,p+2), selon la partition qui manque les numéros.

    Si une partition n'a pas 2 chiffres mais n'est pas vide, alors recurse sur cette partition.

Avec seulement 2 nombres manquants, cet algorithme supprime toujours au moins une partition. Il conserve donc la complexité de O(n) moyenne de Quickselect. De la même façon, avec 3 numéros manquants, cet algorithme supprime également au moins une partition à chaque passage (car, comme pour 2 numéros manquants, au plus une partition seulement contiendra plusieurs numéros manquants). Cependant, je ne suis pas sûr de la diminution de la performance lorsque plusieurs nombres manquants sont ajoutés.

Voici une implémentation qui n'utilise pas le partitionnement sur place. Cet exemple ne répond donc pas à l'espace requis, mais il illustre les étapes de l'algorithme:

<?php

  $list = range(1,100);
  unset($list[3]);
  unset($list[31]);

  findMissing($list,1,100);

  function findMissing($list, $min, $max) {
    if(empty($list)) {
      print_r(range($min, $max));
      return;
    }

    $l = $r = [];
    $pivot = array_pop($list);

    foreach($list as $number) {
      if($number < $pivot) {
        $l[] = $number;
      }
      else {
        $r[] = $number;
      }
    }

    if(count($l) == $pivot - $min - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($min, $pivot-1)) - array_sum($l) . "\n";
    }
    else if(count($l) < $pivot - $min) {
      // more than 1 missing number, recurse
      findMissing($l, $min, $pivot-1);
    }

    if(count($r) == $max - $pivot - 1) {
      // only 1 missing number use difference of sums
      print array_sum(range($pivot + 1, $max)) - array_sum($r) . "\n";
    } else if(count($r) < $max - $pivot) {
      // mroe than 1 missing number recurse
      findMissing($r, $pivot+1, $max);
    }
  }

Démo

7
FuzzyTree

Voici une solution qui utilise k bits de stockage supplémentaire, sans astuces astucieuses et tout simplement. Temps d'exécution O (n), espace supplémentaire O (k). Juste pour prouver que cela peut être résolu sans lire la solution au préalable ou sans être un génie:

void puzzle (int* data, int n, bool* extra, int k)
{
    // data contains n distinct numbers from 1 to n + k, extra provides
    // space for k extra bits. 

    // Rearrange the array so there are (even) even numbers at the start
    // and (odd) odd numbers at the end.
    int even = 0, odd = 0;
    while (even + odd < n)
    {
        if (data [even] % 2 == 0) ++even;
        else if (data [n - 1 - odd] % 2 == 1) ++odd;
        else { int tmp = data [even]; data [even] = data [n - 1 - odd]; 
               data [n - 1 - odd] = tmp; ++even; ++odd; }
    }

    // Erase the lowest bits of all numbers and set the extra bits to 0.
    for (int i = even; i < n; ++i) data [i] -= 1;
    for (int i = 0; i < k; ++i) extra [i] = false;

    // Set a bit for every number that is present
    for (int i = 0; i < n; ++i)
    {
        int tmp = data [i];
        tmp -= (tmp % 2);
        if (i >= even) ++tmp;
        if (tmp <= n) data [tmp - 1] += 1; else extra [tmp - n - 1] = true;
    }

    // Print out the missing ones
    for (int i = 1; i <= n; ++i)
        if (data [i - 1] % 2 == 0) printf ("Number %d is missing\n", i);
    for (int i = n + 1; i <= n + k; ++i)
        if (! extra [i - n - 1]) printf ("Number %d is missing\n", i);

    // Restore the lowest bits again.
    for (int i = 0; i < n; ++i) {
        if (i < even) { if (data [i] % 2 != 0) data [i] -= 1; }
        else { if (data [i] % 2 == 0) data [i] += 1; }
    }
}
7
gnasher729

Pouvez-vous vérifier si chaque numéro existe? Si oui, vous pouvez essayer ceci:

S = somme de tous les nombres dans le sac (S <5050)
Z = somme des nombres manquants 5050 - S

si les nombres manquants sont x et y alors:

x = Z - y et
max (x) = Z - 1

Donc, vous vérifiez la plage de 1 à max(x) et trouvez le numéro

5
Ilian Iliev

Peut-être que cet algorithme peut fonctionner pour la question 1:

  1. Précalculez xor des 100 premiers entiers (val = 1 ^ 2 ^ 3 ^ 4 .... 100)
  2. xor les éléments comme ils continuent à venir du flux d'entrée (val1 = val1 ^ next_input)
  3. réponse finale = val ^ val1

Ou même mieux:

def GetValue(A)
  val=0
  for i=1 to 100
    do
      val=val^i
    done
  for value in A:
    do
      val=val^value 
    done
  return val

Cet algorithme peut en fait être étendu pour deux nombres manquants. La première étape reste la même. Lorsque nous appelons GetValue avec deux nombres manquants, le résultat sera un a1^a2 sont les deux numéros manquants. Disons

val = a1^a2

Maintenant, pour filtrer a1 et a2 de val, prenons n'importe quel bit défini dans val. Disons que le bit ith est défini dans val. Cela signifie que a1 et a2 ont une parité différente à la position ith du bit. Maintenant, nous faisons une autre itération sur le tableau d'origine et gardons deux valeurs xor. Un pour les nombres pour lesquels le bit bit est défini et d'autres pour lesquels le bit n'est pas défini. Nous avons maintenant deux groupes de nombres, et sa garantie que a1 and a2 se trouvera dans des compartiments différents. Répétez maintenant ce que nous avons fait pour trouver un élément manquant dans chaque compartiment.

4
bashrc

Pour Q2, il s’agit d’une solution un peu plus inefficace que les autres, mais elle a toujours O(N) runtime et prend O(k) espace.

L'idée est d'exécuter l'algorithme d'origine deux fois. Dans le premier, vous obtenez un nombre total manquant, ce qui vous donne une limite supérieure des nombres manquants. Appelons ce numéro N. Vous savez que les deux nombres manquants vont totaliser N, le premier chiffre ne peut donc être que dans l'intervalle [1, floor((N-1)/2)] alors que le second est dans [floor(N/2)+1,N-1].

Ainsi, vous bouclez à nouveau tous les numéros, en supprimant tous les numéros qui ne sont pas inclus dans le premier intervalle. Ceux qui sont, vous gardez une trace de leur somme. Enfin, vous connaîtrez l’un des deux chiffres manquants et, par extension, le second.

J'ai le sentiment que cette méthode pourrait être généralisée et peut-être que plusieurs recherches seront exécutées "en parallèle" au cours d'un seul passage sur l'entrée, mais je n'ai pas encore compris comment.

3
Svalorzen

Vous pouvez résoudre Q2 si vous avez la somme des deux listes et le produit des deux listes.

(l1 est l'original, l2 est la liste modifiée)

d = sum(l1) - sum(l2)
m = mul(l1) / mul(l2)

Nous pouvons optimiser cela car la somme d'une série arithmétique est n fois la moyenne des premier et dernier termes:

n = len(l1)
d = (n/2)*(n+1) - sum(l2)

Maintenant nous savons que (si a et b sont les nombres supprimés):

a + b = d
a * b = m

Nous pouvons donc réorganiser pour:

a = s - b
b * (s - b) = m

Et multipliez-vous:

-b^2 + s*b = m

Et réorganiser de sorte que le côté droit est égal à zéro:

-b^2 + s*b - m = 0

Ensuite, nous pouvons résoudre avec la formule quadratique:

b = (-s + sqrt(s^2 - (4*-1*-m)))/-2
a = s - b

Exemple de code Python 3:

from functools import reduce
import operator
import math
x = list(range(1,21))
sx = (len(x)/2)*(len(x)+1)
x.remove(15)
x.remove(5)
mul = lambda l: reduce(operator.mul,l)
s = sx - sum(x)
m = mul(range(1,21)) / mul(x)
b = (-s + math.sqrt(s**2 - (-4*(-m))))/-2
a = s - b
print(a,b) #15,5

Je ne connais pas la complexité des fonctions sqrt, réduire et additionner, je ne peux donc pas comprendre la complexité de cette solution (veuillez commenter ci-dessous, le cas échéant).

3
Tuomas Laakkonen

Il existe un moyen général de généraliser des algorithmes de streaming comme celui-ci. L'idée est d'utiliser un peu de randomisation pour espérer "étendre" les éléments k en sous-problèmes indépendants, où notre algorithme d'origine résout le problème pour nous. Cette technique est utilisée, entre autres, dans la reconstruction de signaux clairsemés.

  • Crée un tableau, a, de taille u = k^2.
  • Choisissez n'importe quel fonction de hachage universelle , h : {1,...,n} -> {1,...,u}. (Comme multiply-shift )
  • Pour chaque i dans 1, ..., n augmenter a[h(i)] += i
  • Pour chaque nombre x dans le flux d'entrée, décrémentez a[h(x)] -= x.

Si tous les nombres manquants ont été hachés dans des compartiments différents, les éléments non nuls du tableau contiendront désormais les nombres manquants.

La probabilité qu'une paire particulière soit envoyée au même compartiment est inférieure à 1/u par définition d'une fonction de hachage universelle. Comme il y a environ k^2/2 paires, nous avons que la probabilité d'erreur est au plus k^2/2/u=1/2. C'est-à-dire que nous réussissons avec une probabilité d'au moins 50%, et si nous augmentons u nous augmentons nos chances.

Notez que cet algorithme prend k^2 logn bits d’espace (nous avons besoin de logn bits par tableau.) Ceci correspond à l’espace requis par la réponse de @Dimitris Andreou (notamment l’espace requis par la factorisation polynomiale également être aléatoire.) Cet algorithme a également un temps constant par mise à jour, plutôt que le temps k dans le cas des sommes de puissance.

En fait, nous pouvons être encore plus efficaces que la méthode de la somme de puissance en utilisant l’astuce décrite dans les commentaires.

2
Thomas Ahle

Je pense que cela peut être fait sans équations et théories mathématiques complexes. Vous trouverez ci-dessous une proposition de solution de complexité temporelle en place et O(2n):

Hypothèses de formulaire d'entrée:

# de nombres dans le sac = n

# de nombres manquants = k

Les nombres dans le sac sont représentés par un tableau de longueur n

Longueur du tableau en entrée pour l'algo = n

Les entrées manquantes dans le tableau (nombres sortis du sac) sont remplacées par la valeur du premier élément du tableau.

Par exemple. Initialement, le sac ressemble à [2,9,3,7,8,6,4,5,1,10]. Si 4 est retiré, la valeur de 4 deviendra 2 (le premier élément du tableau). Par conséquent, après avoir sorti 4, le sac ressemblera à [2,9,3,7,8,6,2,5,1,10]

La clé de cette solution consiste à baliser l’INDEX d’un nombre visité en inversant la valeur de cet INDEX au fur et à mesure que le tableau est parcouru.

    IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers)
    {
        List<int> missingNumbers = new List<int>();
        int arrayLength = arrayOfNumbers.Length;

        //First Pass
        for (int i = 0; i < arrayLength; i++)
        {
            int index = Math.Abs(arrayOfNumbers[i]) - 1;
            if (index > -1)
            {
                arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes
            }
        }

        //Second Pass to get missing numbers
        for (int i = 0; i < arrayLength; i++)
        {                
            //If this index is unvisited, means this is a missing number
            if (arrayOfNumbers[i] > 0)
            {
                missingNumbers.Add(i + 1);
            }
        }

        return missingNumbers;
    }
2
pickhunter

Vous aurez probablement besoin de clarifications sur ce que O(k) signifie.

Voici une solution triviale pour k arbitraire: pour chaque v de votre ensemble de nombres, accumulez la somme de 2 ^ v. À la fin, boucle i de 1 à N. Si somme somme AND AND avec 2 ^ i est zéro, alors il manque. (Ou numériquement, si le plancher de la somme divisé par 2 ^ i est pair. Ou sum modulo 2^(i+1)) < 2^i.)

Facile, non? O(N) heure, O(1) stockage, et il prend en charge k arbitraire.

Sauf que vous calculez des nombres énormes qui, sur un ordinateur réel, nécessiteraient chacun O(N) espace. En fait, cette solution est identique à un vecteur de bits.

Donc, vous pouvez être intelligent et calculer la somme et la somme des carrés et la somme des cubes ... jusqu'à la somme de V ^ k, et faire les calculs de fantaisie pour extraire le résultat. Mais ce sont aussi de gros chiffres, ce qui soulève la question suivante: de quel modèle d'opération abstrait parle-t-on? Combien correspond à l'espace O(1) et combien de temps faut-il pour résumer les nombres de la taille dont vous avez besoin?

2
sfink

Très beau problème. Je préférerais utiliser une différence définie pour Qk. De nombreux langages de programmation le prennent même en charge, comme en Ruby:

missing = (1..100).to_a - bag

Ce n’est probablement pas la solution la plus efficace, mais c’est une solution que j’utiliserais dans la vie réelle si j’étais confronté à une telle tâche dans ce cas (limites connues, limites basses). Si l'ensemble des nombres est très grand, je considérerais évidemment un algorithme plus efficace, mais jusque-là, la solution simple me suffirait.

1
DarkDust

Une autre méthode consiste à utiliser le filtrage de graphe résiduel.

Supposons que nous ayons les numéros 1 à 4 et qu'il manque 3. La représentation binaire est la suivante,

1 = 001b, 2 = 010b, 3 = 011b, 4 = 100b

Et je peux créer un graphique de flux comme suit.

                   1
             1 -------------> 1
             |                | 
      2      |     1          |
0 ---------> 1 ----------> 0  |
|                          |  |
|     1            1       |  |
0 ---------> 0 ----------> 0  |
             |                |
      1      |      1         |
1 ---------> 0 -------------> 1

Notez que le graphe de flux contient x nœuds, x étant le nombre de bits. Et le nombre maximum d'arêtes est (2 * x) -2.

Ainsi, pour un entier de 32 bits, il faudra O(32) espace ou O(1) espace.

Maintenant, si je supprime la capacité de chaque nombre à partir de 1,2,4, il me reste un graphe résiduel.

0 ----------> 1 ---------> 1

Enfin, je ferai une boucle comme celle-ci,

 result = []
 for x in range(1,n):
     exists_path_in_residual_graph(x)
     result.append(x)

Le résultat est maintenant dans result contient des nombres qui ne manquent pas non plus (faux positif). Mais k <= (taille du résultat) <= n quand il y a k éléments manquants.

Je vais parcourir la liste donnée une dernière fois pour marquer le résultat comme manquant ou non.

La complexité temporelle sera donc O(n).

Enfin, il est possible de réduire le nombre de faux positifs (et l'espace requis) en prenant les nœuds 00, 01, 11, 10 au lieu de simplement 0 et 1.

1
shuva

Vous pouvez essayer d'utiliser Bloom Filter . Insérez chaque nombre dans le sac dans la floraison, puis effectuez une itération sur le jeu complet de 1 k jusqu'à ce que chaque nombre ne soit pas trouvé. Cela peut ne pas trouver la réponse dans tous les scénarios, mais pourrait être une bonne solution.

1
jdizzle

J'adopterais une approche différente pour répondre à cette question et demanderais à l'intervieweur plus de détails sur le problème plus vaste qu'il tentait de résoudre. En fonction du problème et des exigences qui l’entourent, la solution évidente basée sur un ensemble peut être la bonne chose, et non pas l’approche consistant à générer une liste et une sélection après coup.

Par exemple, il se peut que l'intervieweur envoie des messages n et qu'il doit connaître le k qui n'a pas abouti à une réponse et qu'il doit le savoir le plus rapidement possible. après le n-k la réponse arrive. Supposons également que la nature du canal de messages soit telle que, même si tout fonctionne à merveille, vous disposez de suffisamment de temps pour traiter certains messages sans avoir aucune incidence sur le temps requis pour obtenir le résultat final après l'arrivée de la dernière réponse. Vous pouvez utiliser ce temps en insérant une facette d’identification de chaque message envoyé dans un ensemble et en le supprimant au fur et à mesure que chaque réponse correspondante arrive. Une fois que la dernière réponse est arrivée, la seule chose à faire est de supprimer son identifiant de l'ensemble, ce qui, dans les implémentations classiques, prend O(log k+1). Après cela, le jeu contient la liste des éléments k manquants et il n’ya pas de traitement supplémentaire à effectuer.

Ce n’est certainement pas l’approche la plus rapide pour le traitement par lots de sacs de nombres pré-générés, car tout fonctionne sous O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k)). Mais cela fonctionne pour toute valeur de k (même si elle n'est pas connue à l'avance) et dans l'exemple ci-dessus, il a été appliqué de manière à minimiser l'intervalle le plus critique.

1
Blrfl

Vous pouvez motiver la solution en y réfléchissant sous l’angle des symétries (groupes, en langage mathématique). Peu importe l'ordre des nombres, la réponse devrait être la même. Si vous envisagez d'utiliser les fonctions k pour déterminer les éléments manquants, vous devriez réfléchir aux fonctions qui possèdent cette propriété: symétrique. La fonction s_1(x) = x_1 + x_2 + ... + x_n est un exemple de fonction symétrique, mais il en existe d'autres de degré supérieur. En particulier, considérons le fonctions symétriques élémentaires. La fonction symétrique élémentaire du degré 2 est s_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n, la somme de tous les produits de deux éléments. De même pour les fonctions symétriques élémentaires de degré 3 et supérieur. Ils sont évidemment symétriques. De plus, il s’avère qu’ils sont les éléments de base de toutes les fonctions symétriques.

Vous pouvez construire les fonctions symétriques élémentaires au fur et à mesure en notant que s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1)). Une réflexion plus poussée devrait vous convaincre que s_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1)) et ainsi de suite, afin qu’ils puissent être calculés en un seul passage.

Comment pouvons-nous savoir quels éléments manquaient dans le tableau? Pensez au polynôme (z-x_1)(z-x_2)...(z-x_n). Il est évalué à 0 si vous entrez l'un des nombres x_i. En développant le polynôme, vous obtenez z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n. Les fonctions symétriques élémentaires apparaissent également ici, ce qui n’est pas vraiment surprenant, car le polynôme devrait rester le même si nous appliquons une permutation quelconque aux racines.

Nous pouvons donc construire le polynôme et essayer de le factoriser pour déterminer quels nombres ne sont pas dans la série, comme d'autres l'ont mentionné.

Enfin, si nous sommes préoccupés par le débordement de mémoire avec de grands nombres (le nième polynôme symétrique sera de l’ordre 100!), nous pouvons faire ces calculs mod pp est un nombre premier plus grand que 100. Dans ce cas, nous évaluons le polynôme mod p et nous trouvons qu’il est à nouveau évalué à 0 lorsque l’entrée est un nombre dans l’ensemble et à une valeur non nulle lorsque l’entrée est un nombre pas dans l'ensemble. Cependant, comme d'autres l'ont souligné, pour obtenir les valeurs du polynôme dans un temps qui dépend de k et non de N, nous devons factoriser le polynôme mod p.

1
Edward Doolittle

Je pense que cela peut être généralisé comme ceci:

Notons S, M comme les valeurs initiales pour la somme des séries arithmétiques et de la multiplication.

S = 1 + 2 + 3 + 4 + ... n=(n+1)*n/2
M = 1 * 2 * 3 * 4 * .... * n 

Je devrais penser à une formule pour calculer cela, mais ce n'est pas le but. Quoi qu'il en soit, s'il manque un numéro, vous avez déjà fourni la solution. Cependant, si deux nombres sont manquants, notons la nouvelle somme et le total total par S1 et M1, qui seront comme suit:

S1 = S - (a + b)....................(1)

Where a and b are the missing numbers.

M1 = M - (a * b)....................(2)

Puisque vous connaissez S1, M1, M et S, l’équation ci-dessus peut être résolue pour trouver a et b, les nombres manquants.

Maintenant pour les trois numéros manquants:

S2 = S - ( a + b + c)....................(1)

Where a and b are the missing numbers.

M2 = M - (a * b * c)....................(2)

Maintenant, votre inconnu est 3 alors que vous ne pouvez résoudre que deux équations.

0
Jack_of_All_Trades

Nous pouvons faire le Q1 et Q2 dans O (log n) la plupart du temps.

Supposons que notre memory chip consiste en un tableau de n nombre de test tubes. Et un nombre x dans le tube à essai est représenté par xmilliliter de produit chimique-liquide.

Supposons que notre processeur soit un laser light. Lorsque nous allumons le laser, il traverse tous les tubes perpendiculairement à sa longueur. Chaque fois qu'il traverse le liquide chimique, la luminosité est réduite de 1. Et passer la lumière à une certaine marque millilitre est une opération de O(1).

Maintenant, si nous allumons notre laser au milieu du tube à essai et obtenons la sortie de luminosité

  • est égal à une valeur précalculée (calculée quand aucun nombre ne manquait), les nombres manquants sont supérieurs à n/2.
  • Si notre sortie est inférieure, il reste au moins un nombre inférieur à n/2. Nous pouvons également vérifier si la luminosité est réduite de 1 ou 2. s'il est réduit de 1 alors un nombre manquant est inférieur à n/2 et l'autre est supérieur à n/2. S'il est réduit de 2, les deux nombres sont inférieurs à n/2.

Nous pouvons répéter le processus ci-dessus à plusieurs reprises en réduisant notre domaine de problèmes. A chaque étape, nous réduisons le domaine de moitié. Et finalement nous pouvons arriver à notre résultat.

Algorithmes parallèles qui méritent d'être mentionnés (parce qu'ils sont intéressants),

  • le tri par un algorithme parallèle, par exemple, la fusion parallèle peut être effectué dans le temps O(log^3 n). Et puis le nombre manquant peut être trouvé par une recherche binaire dans O(log n) time.
  • Théoriquement, si nous avons les processeurs n, chaque processus peut vérifier l'une des entrées et définir un indicateur identifiant le nombre (de manière pratique dans un tableau). Et à l'étape suivante, chaque processus peut vérifier chaque indicateur et finalement générer le nombre qui n'est pas indiqué. L'ensemble du processus prendra O(1) time. O(n) additionnel/espace requis.

Notez que les deux algorithmes parallèles fournis ci-dessus peuvent nécessiter de l'espace supplémentaire, comme mentionné dans le commentaire.

0
shuva

Je crois que j'ai un algorithme d'espace O(k) time et O(log(k)), étant donné que vous disposez des fonctions floor(x) et log2(x) pour les entiers arbitrairement grands disponibles:

Vous avez un entier k- long (d'où l'espace log8(k)) auquel vous ajoutez le x^2, où x est le prochain numéro que vous trouverez dans le sac: s=1^2+2^2+... Ceci prend O(N) time ( ce qui n’est pas un problème pour l’intervieweur). À la fin, vous obtenez j=floor(log2(s)) qui est le plus grand nombre que vous recherchez. Puis s=s-j et vous refaites ce qui précède:

for (i = 0 ; i < k ; i++)
{
  j = floor(log2(s));
  missing[i] = j;
  s -= j;
}

Maintenant, vous n'avez généralement pas les fonctions floor et log2 pour les entiers 2756-bit, mais plutôt pour les doubles. Alors? Simplement, pour chaque 2 octets (ou 1, 3 ou 4), vous pouvez utiliser ces fonctions pour obtenir les nombres souhaités, mais cela ajoute un facteur O(N) à la complexité temporelle.

0
CostasGR43

Disons qu'un objet ArrayList (myList) est rempli avec ces nombres et qu'il en manque deux. X et y manquent. La solution possible peut donc être:

int k = 1;
        while (k < 100) {
            if (!myList.contains(k)) {
                System.out.println("Missing No:" + k);
            }
            k++;
        }
0
SagarS

Cela peut paraître stupide, mais, dans le premier problème qui vous est présenté, il vous faudrait voir tous les nombres restants dans le sac pour les additionner afin de trouver le nombre manquant à l’aide de cette équation.

Donc, puisque vous voyez tous les chiffres, cherchez le nombre qui manque. La même chose vaut pour deux numéros manquants. Assez simple je pense. Inutile d'utiliser une équation pour voir les nombres restant dans le sac.

0
Stephan M

Je ne sais pas si cela est efficace ou non, mais je voudrais suggérer cette solution.

  1. Calculer xor des 100 éléments
  2. Calculer xor des 98 éléments (après avoir enlevé les 2 éléments)
  3. Maintenant (résultat de 1) XOR (résultat de 2) vous donne le xor des deux non manquants i..e a ​​XOR b si a et b sont les éléments manquants
    4.Obtenez la somme des numéros manquants avec votre approche habituelle de la formule de somme diff et disons que le diff est d.

Exécutez maintenant une boucle pour obtenir les paires possibles (p, q), qui se trouvent toutes deux dans [1, 100] et dont la somme est d.

Quand une paire est obtenue, vérifiez si (résultat de 3) XOR p = q et si oui, nous avons terminé.

S'il vous plaît, corrigez-moi si je me trompe et commentez la complexité du temps si cela est correct

0
user2221214