Comment trouver 5 valeurs répétées en temps O (n)?


15

Supposons que vous ayez un tableau de taille contenant des entiers de à inclus, avec exactement cinq répétitions. J'ai besoin de proposer un algorithme qui puisse trouver les nombres répétés en temps . Je ne peux, pour ma vie, penser à rien. Je pense que le tri, au mieux, serait ? La traversée du tableau serait alors , résultant en . Cependant, je ne suis pas vraiment sûr que le tri soit nécessaire car j'ai vu des choses délicates avec une liste liée, des files d'attente, des piles, etc.n61n5O(n)O(nlogn)O(n)O(n2logn)


16
O ( n 2 log n ) O ( n log n )O(nlogn)+O(n) n'est pas . C'est . Ce serait O ( n 2 log n ) si vous avez fait le tri n fois. O(n2logn)O(nlogn)O(n2logn)
Fund Monica's Lawsuit


1
@leftaroundabout Ces algorithmes sont O(kn)n est la taille du tableau et k est la taille de l'ensemble d'entrée. puisque k=nconstant ces algorithmes fonctionnent en O(n2)
Roman Gräf

4
@ RomanGräf il semble que la situation actuelle soit la suivante: les algorithmes fonctionnent en O(logkn) , où k est la taille du domaine. Donc, pour un problème comme celui de l'OP, cela revient au même si vous utilisez un tel algorithme sur le domaine de taille n , ou un algorithme traditionnel O(nlogn) sur un domaine de taille illimitée. Est également logique.
leftaroundabout

5
Pour n=6 , le seul nombre autorisé est 1 , selon votre description. Mais alors 1 devrait être répétée six, pas cinq, fois.
Alex Reinking

Réponses:


22

Vous pouvez créer un tableau supplémentaire de taille n . Définissez initialement tous les éléments du tableau sur 0 . Faites ensuite une boucle dans le tableau d'entrée A et augmentez B [ A [ i ] ] de 1 pour chaque i . Après cela, vous vérifiez simplement le tableau B : boucle sur A et si B [ A [ i ] ] > 1, alors A [ i ] est répété. Vous le résolvez dans O ( n )Bn0AB[A[i]]iBAB[A[i]]>1A[i]O(n)temps au prix de la mémoire qui est et parce que vos entiers sont compris entre 1 et n - 5 .O(n)1n5


26

La solution dans la réponse de fade2black est la solution standard, mais elle utilise l' espace . Vous pouvez améliorer cela à l' espace O ( 1 ) comme suit:O(n)O(1)

  1. Soit le tableau . Pour d = 1 , , 5 , calculer σ d = n i = 1 A [ i ] d .A[1],,A[n]d=1,,5σd=i=1nA[i]d
  2. Calculez (vous pouvez utiliser les formules bien connues pour calculer cette dernière somme dans O ( 1 ) ). Notez que τ d = m d 1 + + m d 5 , où m 1 , , m 5 sont les nombres répétés.τd=σdi=1n5idO(1)τd=m1d++m5dm1,,m5
  3. Calculez le polynôme . Les coefficients de ce polynôme sont des fonctions symétriques de m 1 , , m 5 qui peuvent être calculées à partir de τ 1 , , τ 5 dans O ( 1 ) .P(t)=(tm1)(tm5)m1,,m5τ1,,τ5O(1)
  4. Trouvez toutes les racines du polynôme en essayant toutes les n - 5 possibilités.P(t)n5

Cet algorithme suppose le modèle de machine RAM, dans lequel les opérations arithmétiques de base sur les mots bits prennent O ( 1 ) .O(logn)O(1)


Une autre façon de formuler cette solution est la suivante:

  1. Calculez et déduisez y 1 = m 1 + + m 5 en utilisant la formule y 1 = x 1 - n - 5 i = 1 i .x1=i=1nA[i]y1=m1++m5y1=x1i=1n5i
  2. Calculer dans O ( n ) en utilisant la formule x 2 = ( A [ 1 ] ) A [ 2 ] + ( A [ 1 ] + A [ 2 ] ) A [ 3 ] + ( A [ 1x2=1i<jA[i]A[j]O(n)
    x2=(A[1])A[2]+(A[1]+A[2])A[3]+(A[1]+A[2]+A[3])A[4]++(A[1]++A[n1])A[n].
  3. Déduire utilisant la formule y 2 = x 2 - 1 i < j n - 5 i j - ( n - 5 i = 1 i ) y 1 .y2=1i<j5mimj
    y2=x21i<jn5ij(i=1n5i)y1.
  4. Calculez et déduisez y 3 , y 4 , y 5 le long de lignes similaires.x3,x4,x5y3,y4,y5
  5. Les valeurs de sont (jusqu'au signe) les coefficients du polynôme P ( t ) de la solution précédente.y1,,y5P(t)

Cette solution montre que si nous remplaçons 5 par , nous obtenons (je crois) un algorithme O ( d 2 n ) utilisant l' espace O ( d 2 ) , qui effectue des opérations arithmétiques O ( d n ) sur des entiers de longueur de bit O ( d log n ) , en gardant au plus O ( d ) de ceux-ci à un moment donné. (Cela nécessite une analyse minutieuse des multiplications que nous effectuons, dont la plupart impliquent un opérande de longueur uniquement O ( log ndO(d2n)O(d2)O(dn)O(dlogn)O(d) .) Il est concevable que cela puisse être amélioré en temps O ( d n ) et enespace O ( d ) en utilisant l'arithmétique modulaire.O(logn)O(dn)O(d)


Une interprétation de et τ d , P ( t ) , m i et ainsi de suite? Pourquoi d { 1 , 2 , 3 , 4 , 5 } ? σdτdP(t)mid{1,2,3,4,5}
mouche en polystyrène voler

3
L'idée derrière la solution est l' astuce de sommation , qui apparaît dans de nombreux exercices (par exemple, comment trouvez-vous l'élément manquant dans un tableau de longueur contenant tous sauf un des nombres 1 , , n ?). L'astuce de sommation peut être utilisée pour calculer f ( m 1 ) + + f ( m 5 ) pour une fonction arbitraire f , et la question est de savoir quel f choisir pour pouvoir déduire m 1 , , mn11,,nf(m1)++f(m5)ff . Ma réponse utilise des astuces familières de la théorie élémentaire des fonctions symétriques. m1,,m5
Yuval Filmus

1
@hoffmale En fait, . O(d2)
Yuval Filmus

1
@hoffmale Chacun d'eux prend mots machine. d
Yuval Filmus

1
@BurnsBA Le problème avec cette approche est que est beaucoup plus grand que ( n - 4 ) ( n - 5 )(n5)# . Les opérations sur de grands nombres sont plus lentes. (n4)(n5)2
Yuval Filmus,

8

Il existe également un algorithme de temps linéaire et d'espace constant basé sur le partitionnement, qui peut être plus flexible si vous essayez de l'appliquer à des variantes du problème sur lesquelles l'approche mathématique ne fonctionne pas bien. Cela nécessite de muter le tableau sous-jacent et présente des facteurs constants pires que l'approche mathématique. Plus précisément, je pense que les coûts en termes du nombre total de valeurs et du nombre de doublons d sont respectivement O ( n log d ) et O ( d ) , bien que le prouver rigoureusement prendra plus de temps que je n'en ai actuellement .ndO(nlogd)O(d)


Algorithme

Commencez avec une liste de paires, où la première paire est la plage sur l'ensemble du tableau, ou si indexé 1.[(1,n)]

Répétez les étapes suivantes jusqu'à ce que la liste soit vide:

  1. Prenez et supprimez toute paire de la liste.(i,j)
  2. Trouvez le minimum et le maximum, et max , du sous-tableau noté.minmax
  3. Si , le sous-tableau se compose uniquement d'éléments égaux. Donnez ses éléments sauf un et ignorez les étapes 4 à 6.min=max
  4. Si , le sous-tableau ne contient aucun doublon. Ignorez les étapes 5 et 6.maxmin=ji
  5. Partitionnez le sous-tableau autour de , de sorte que les éléments jusqu'à un certain indiceksont plus petits que le séparateur et les éléments au-dessus de cet indice ne le sont pas.min+max2k
  6. Ajoutez et ( k + 1 , j ) à la liste.(i,k)(k+1,j)

Analyse cursive de la complexité temporelle.

Les étapes 1 à 6 prennent du temps , car la recherche du minimum et du maximum et le partitionnement peuvent être effectués en temps linéaire.O(ji)

Chaque paire de la liste est soit la première paire, ( 1 , n ) , soit un enfant d'une paire pour laquelle le sous-tableau correspondant contient un élément en double. Il y a au plus d log 2 n + 1 ⌉ de tels parents, car chaque parcours divise par deux la plage dans laquelle un doublon peut être, il y a donc au plus 2 d log 2 n + 1 ⌉ au total lorsque des paires sont incluses sur des sous-réseaux sans doublons. À tout moment, la taille de la liste ne dépasse pas 2 jours(i,j)(1,n)dlog2n+12dlog2n+12d.

Considérez le travail pour trouver un double. Cela consiste en une séquence de paires sur une plage exponentiellement décroissante, donc le travail total est la somme de la séquence géométrique, ou . Cela produit un corollaire évident que le travail total pour les doublons d doit être O ( n d ) , qui est linéaire en n .O(n)dO(nd)n

Pour trouver une limite plus stricte, considérons le pire des cas de doublons répartis au maximum. Intuitivement, la recherche prend deux phases, l'une où le tableau complet est parcouru à chaque fois, en parties progressivement plus petites, et l'autre où les parties sont plus petites que donc seules les parties du tableau sont traversées. La première phase ne peut être quelogd enprofondeur, donc a coûtéO(nlogd), et la deuxième phase a coûtéO(n)parce que la superficie totale recherchée diminue à nouveau de façon exponentielle.ndlogdO(nlogd)O(n)


Merci pour l'explication. Maintenant, je comprends. Un très joli algorithme!
DW

5

Laissant cela comme une réponse car il a besoin de plus d'espace qu'un commentaire.

Vous faites une erreur dans l'OP lorsque vous suggérez une méthode. Trier une liste puis la traverser temps, pas O ( n 2 log n ) temps. Lorsque vous faites deux choses (qui prennent O ( f ) et O ( g ) respectivement) séquentiellement, la complexité temporelle résultante est O ( f + g ) = O ( max f , g ) (dans la plupart des circonstances).O(nlogn)O(n2logn)O(f)O(g)O(f+g)=O(maxf,g)

Afin de multiplier les complexités temporelles, vous devez utiliser une boucle for. Si vous avez une boucle de longueur et que pour chaque valeur de la boucle vous faites une fonction qui prend O ( g ) , alors vous aurez le temps O ( f g ) .fO(g)O(fg)

Donc, dans votre cas, vous triez dans puis transversalement dans O ( n ) résultant en O ( n log n + n ) = O ( n log n ) . Si pour chaque comparaison de l'algorithme de tri vous deviez faire un calcul qui prend O ( n ) , alors il faudrait O ( n 2 log n ) mais ce n'est pas le cas ici.O(nlogn)O(n)O(nlogn+n)=O(nlogn)O(n)O(n2logn)


Au cas où vous seriez curieux de savoir si j'affirme que , il est important de noter que ce n'est pas toujours vrai. Mais si f O ( g ) ou g O ( f ) (qui vaut pour toute une série de fonctions communes), il le sera. Le moment le plus courant qu'il ne tient pas est lorsque des paramètres supplémentaires sont impliqués et que vous obtenez des expressions comme O ( 2 c n + n log n ) .O(f+g)=O(maxf,g)fO(g)gO(f)O(2cn+nlogn)


3

Il existe une variante en place évidente de la technique de tableau booléen utilisant l'ordre des éléments comme magasin (où arr[x] == xpour les éléments "trouvés"). Contrairement à la variante de partition qui peut être justifiée d'être plus générale, je ne sais pas quand vous auriez réellement besoin de quelque chose comme ça, mais c'est simple.

for idx from n-4 to n
    while arr[arr[idx]] != arr[idx]
        swap(arr[arr[idx]], arr[idx])

Cela place simplement à plusieurs reprises arr[idx]l'emplacement arr[idx]jusqu'à ce que vous trouviez cet emplacement déjà pris, auquel cas il doit s'agir d'un doublon. Notez que le nombre total de swaps est limité par puisque chaque swap rend sa condition de sortie correcte.n


Vous devrez donner une sorte d'argument selon lequel la whileboucle interne s'exécute en temps constant en moyenne. Sinon, ce n'est pas un algorithme à temps linéaire.
David Richerby

@DavidRicherby Il ne s'exécute pas en temps constant en moyenne, mais la boucle externe ne fonctionne que 5 fois, donc ça va. Notez que le nombre total de swaps est limité par puisque chaque swap rend sa condition de sortie correcte, donc même si le nombre de valeurs en double augmente, le temps total est toujours linéaire (aka. Il prend n étapes plutôt que n d ). nnnd
Veedrac

Oups, je n'ai pas remarqué que la boucle externe s'exécute un nombre constant de fois! (Modifié pour inclure votre note sur le nombre de swaps et aussi pour que je puisse inverser mon downvote.)
David Richerby

1

Soustrayez les valeurs que vous avez de la somme .i=1ni=(n1)n2

Donc, après le temps (en supposant que l'arithmétique est O (1), ce qui n'est pas vraiment, mais supposons), vous avez une somme σ 1 de 5 entiers entre 1 et n:Θ(n)σ1

x1+x2+x3+x4+x5=σ1

Soi-disant, ce n'est pas bon, non? Vous ne pouvez pas comprendre comment diviser cela en 5 nombres distincts.

i=1ni2

x12+x22+x32+x42+x52=σ2

x

log(5n6)


@YuvalFilmus ne propose-t-il pas la même solution?
fade2black

@ fade2black: Oh, oui, c'est le cas, désolé, je viens de voir la première ligne de sa solution.
einpoklum


0

Mappez un tableau sur 1 << A[i]puis XOR tout ensemble. Vos doublons seront les numéros où le bit correspondant est désactivé.


Il y a cinq doublons, donc l'astuce xor ne se cassera pas dans certains cas.
Evil

1
O(n2)nO(n)O(n2)

O(1)O(n)

1
nO(n)O(1)n/64nnn/64O(n)O(1)

nnO(kn)knk=64O(1)O(k)O(n)O(kn)k

-2
DATA=[1,2,2,2,2,2]

from collections import defaultdict

collated=defaultdict(list):
for item in DATA:
    collated[item].append(item)
    if len(collated) == 5:
        return item.

# n time

4
Bienvenue sur le site. Nous sommes un ordinateur scientifique site, nous cherchons donc des algorithmes et des explications, pas décharges de code qui exigent la compréhension d'une langue particulière et ses bibliothèques. En particulier, votre affirmation selon laquelle ce code s'exécute en temps linéaire suppose qu'il collated[item].append(item)s'exécute en temps constant. Est-ce vraiment vrai?
David Richerby

3
Vous recherchez également une valeur qui est répétée cinq fois. En revanche, l'OP recherche cinq valeurs, qui sont chacune répétées deux fois.
Yuval Filmus
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.