58 votes

Algorithme plus rapide pour trouver unique élément entre deux tableaux?

EDIT: Pour tous ceux qui à cette question, j'ai posté une réponse à clarifier ce qui se passait. L'on a accepté la réponse est celle que j'ai le mieux répond à ma question posté, mais pour plus de détails, se référer à ma réponse.

REMARQUE: Ce problème a été à l'origine de pseudo et de listes. J'ai adapté à Java et des tableaux. Ainsi, alors que j'adorerais voir de toutes les solutions qui utilisent Java astuces spécifiques (ou des astuces dans n'importe quelle langue d'ailleurs!), rappelez-vous que l'origine du problème est indépendant de la langue.

Le Problème

Disons qu'il y a deux non triés entier tableaux a et b, avec élément de répétition permis. Ils sont identiques (à l'égard des éléments contenus) à l'exception de l'un des tableaux a un élément supplémentaire. À titre d'exemple:

int[] a = {6, 5, 6, 3, 4, 2};
int[] b = {5, 7, 6, 6, 2, 3, 4};

La conception d'un algorithme qui prend en entrée de ces deux tableaux, et les sorties de l'unique entier (dans le cas ci-dessus, 7).

La Solution (Pour L'Instant)

J'ai trouvé ceci:

public static int getUniqueElement(int[] a, int[] b) {
    int ret = 0;
    for (int i = 0; i < a.length; i++) {
        ret ^= a[i];
    }
    for (int i = 0; i < b.length; i++) {
        ret ^= b[i];
    }
    return ret;
}

Le "officielle" de la solution présentée dans la classe:

public static int getUniqueElement(int[] a, int[] b) {
    int ret = 0;
    for (int i = 0; i < a.length; i++) {
        ret += a[i];
    }
    for (int i = 0; i < b.length; i++) {
        ret -= b[i];
    }
    return Math.abs(ret);
}

Oui, les deux sont conceptuellement faire la même chose. Et étant donné que l' a est de longueur m et b est de longueur n, alors les deux solutions ont des temps d'exécution de O(m + n).

La Question

Plus tard j'ai à parler avec mon professeur et il a laissé entendre qu'il y avait une encore plus rapide façon de faire. Honnêtement, je ne vois pas comment déterminer si un élément est unique, il semble que vous auriez à au moins regarder chaque élément. À qui est au moins O(m + n)...droit?

Donc, il y a un moyen plus rapide? Et si oui, quel est-il?

28voto

Shashank Points 3232

C'est probablement le plus rapide, vous pouvez le faire en Java à l'aide de HotLick de la suggestion dans les commentaires. Il fait l'hypothèse qu' b.length == a.length + 1 b est le plus grand tableau, avec le supplément "unique" de l'élément.

public static int getUniqueElement(int[] a, int[] b) {
    int ret = 0;
    int i;
    for (i = 0; i < a.length; i++) {
        ret = ret ^ a[i] ^ b[i];
    }
    return ret ^ b[i];
}

Même si l'hypothèse ne peut pas être fait, vous pouvez facilement étendre pour inclure le cas où a ou b peut être le plus grand tableau avec l'élément unique. Il est toujours en O(m+n) si et seulement en boucle/affectation de la charge du système est réduite.

Edit:

En raison des détails de mise en œuvre de la langue, c'est encore (étonnamment) le moyen le plus rapide pour faire de Disponible.

def getUniqueElement1(A, B):
    ret = 0
    for a in A: ret = ret ^ a
    for b in B: ret = ret ^ b
    return ret

J'ai testé cela avec l' timeit module et trouvé des résultats intéressants. Il s'avère que le longhand ret = ret ^ a est en effet plus rapide en Python que l'abréviation ret ^= a. Aussi une itération sur les éléments d'une boucle est beaucoup, beaucoup plus rapide que de parcourir l'index, puis en faisant indice des opérations en Python. C'est pourquoi ce code est beaucoup plus rapide que ma méthode précédente où j'ai essayé de copier Java.

Je crois que la morale de l'histoire est qu'il n'y a pas de bonne réponse, car la question est bidon de toute façon. Comme l'OP a noté dans une autre réponse ci-dessous, il s'avère que vous ne pouvez pas vraiment aller plus vite que O(m+n) sur ce et son professeur était simplement de tirer sa jambe. Ainsi, le problème se réduit à trouver le moyen le plus rapide pour parcourir tous les éléments dans les deux tableaux, et l'accumulation de la XOR de tous. Et cela signifie qu'il est entièrement dépendant de la langue mise en œuvre, et que vous avez à faire quelques tests et de jouer autour pour obtenir le vrai "le plus rapide" solution quelle que soit mise en œuvre que vous utilisez, parce que l'ensemble de l'algorithme ne va pas changer.

14voto

William Gaul Points 1714

Bien, nous y voilà...mes excuses à ceux qui attendent une solution plus rapide. Il s'avère que mon professeur était d'avoir un peu de plaisir avec moi et j'ai complètement raté le point de ce qu'il disait.

Je devrais commencer par clarifier ce que je voulais dire par:

il a laissé entendre qu'il y avait une encore plus rapide façon de faire

L'essentiel de notre conversation était: il a dit que mon XOR approche est intéressante, et nous avons parlé pendant un certain temps sur façon dont je suis arrivé à ma solution. Il m'a demandé si je pensais que ma solution est optimale. Je l'ai dit je n'ai (pour les raisons que j'ai mentionné dans ma question). Puis il m'a demandé, "Êtes-vous sûr?" avec un regard sur son visage, je peux seulement décrire comme "sûr". J'ai hésité, mais a dit oui. Il m'a demandé si je pouvais penser à une meilleure façon de le faire. J'étais un peu comme, "Tu veux dire qu'il y a un moyen plus rapide?" mais au lieu de me donner une réponse claire, il m'a dit de penser. J'ai dit que je le ferais.

Alors j'ai pensé à elle, assurez-vous que mon professeur savait quelque chose que j'ignorais. Et après ne vient pas avec quoi que ce soit pour un jour, je suis venu ici.

Ce que mon professeur en réalité je voulais me faire a été de défendre ma solution comme étant optimal, ne pas essayer de trouver une meilleure solution. Comme il l'a dit: la création d'une belle algorithme est la partie la plus facile, le plus dur est de prouver qu'il fonctionne (et que c'est la meilleure). Il pensait que c'était assez drôle que j'ai passé beaucoup de temps à Trouver-Une-Meilleure-Façon de Terre au lieu d'un simple preuve de O(n) qui aurait pris beaucoup moins de temps (nous avons fini par le faire, voir ci-dessous si vous êtes intéressé).

Donc, je suppose, la grande leçon ici. Je vais être accepter Shashank Gupta réponse car je pense qu'il ne parviennent à répondre à la question initiale, même si la question a été entaché d'irrégularités.

Je vais vous laisser là avec un joli petit Python one-liner que j'ai trouvé en tapant la preuve. C'est pas plus efficace, mais je l'aime:

def getUniqueElement(a, b):
    return reduce(lambda x, y: x^y, a + b)

Un Très Informelle "Preuve"

Commençons par les deux tableaux de la question, a et b:

int[] a = {6, 5, 6, 3, 4, 2};
int[] b = {5, 7, 6, 6, 2, 3, 4};

Nous nous contenterons de dire ici que le plus court tableau a longueur n, puis le long de tableau doit avoir la longueur n + 1. La première étape est de prouver linéaire complexité est d'ajouter des tableaux dans un troisième tableau (que nous appellerons c):

int[] c = {6, 5, 6, 3, 4, 2, 5, 7, 6, 6, 2, 3, 4};

qui a une longueur 2n + 1. Pourquoi faire cela? Eh bien, maintenant nous avons un autre problème: trouver l'élément qui se produit d'un nombre impair de fois, en c (à partir d'ici "nombre impair de fois" et "unique" sont prises pour dire la même chose). C'est en fait une assez populaire question d'entrevue et, apparemment, est où mon professeur a eu l'idée pour son problème, alors maintenant ma question a une certaine importance pratique. Hourra!

Imaginons qu'il y est un algorithme plus rapide que O(n), tel que O(log n). Ce que cela signifie, c'est qu'il aura uniquement accès à certains des éléments de l' c. Par exemple, un O(log n) algorithme pourrait seulement avoir à vérifier les journaux(13) ~ 4 des éléments dans notre exemple de tableau pour déterminer l'élément unique. Notre question est, est-ce possible?

D'abord, nous allons voir si nous pouvons en tirer avec la suppression de tous les éléments (par "retrait", je veux dire de ne pas avoir à y accéder). Que diriez-vous si nous enlever 2 éléments, de sorte que notre algorithme vérifie seulement un subarray d' c avec une longueur 2n - 1? C'est toujours linéaire de la complexité, mais si nous pouvons faire cela, alors peut-être que nous pouvons nous améliorer encore plus.

Donc, nous allons choisir deux éléments d' c complètement au hasard de les enlever. Il ya en fait plusieurs choses qui pourraient arriver ici, que je vais résumer en cas:

// Case 1: Remove two identical elements
{6, 5, 6, 3, 4, 2, 5, 7, 2, 3, 4};

// Case 2: Remove the unique element and one other element
{6, 6, 3, 4, 2, 5, 6, 6, 2, 3, 4};

// Case 3: Remove two different elements, neither of which are unique
{6, 5, 6, 4, 2, 5, 7, 6, 6, 3, 4};

Que fait notre tableau ressemble maintenant? Dans le premier cas, 7 est toujours l'élément unique. Dans le second cas, il est un nouvel élément unique, 5. Et dans le troisième cas, il y a maintenant 3 éléments uniques...ouais c'est un désordre total.

Maintenant notre question devient: peut-on déterminer l'élément unique de l' c juste en regardant ce subarray? Dans le premier cas, nous voyons que 7 est l'élément unique de la subarray, mais nous ne pouvons pas être sûr que c'est aussi l'unique élément de l' c; les deux éléments supprimés aurait pu tout aussi bien pu s'7 et 1. Un raisonnement similaire s'applique pour le deuxième cas. Dans le cas 3, avec 3 éléments uniques, nous n'avons aucun moyen de dire à qui deux sont non-unique en c.

Il devient clair que même avec 2n - 1 d'accès, il n'y a pas suffisamment d'informations pour résoudre le problème. Et donc, la solution optimale est linéaire.

Bien sûr, une vraie preuve d'utilisation de l'induction et de ne pas utiliser la preuve par l'exemple, mais je vais laisser ça à quelqu'un d'autre :)

7voto

Peter Lawrey Points 229686

Vous pouvez stocker le comte de chaque valeur dans une collection comme un tableau ou d'un tableau associatif. O(n), alors vous pouvez vérifier les valeurs de l'autre de collecte et de s'arrêter dès que vous savez que vous avez manqué le match. Cela pourrait signifier que vous recherchez seulement la moitié de la deuxième tableau en moyenne.

3voto

A. I. Breveleri Points 469

C'est un petit peu plus rapide:

public static int getUniqueElement(int[] a, int[] b) {
    int ret = 0;
    int i;
    for (i = 0; i < a.length; i++) {
        ret += (a[i] - b[i]);
    }
    return Math.abs(ret - b[i]);
}

Il est O(m), mais l'ordre ne sont pas raconter toute l'histoire. La boucle de la partie de la "officielle" de la solution a environ 3 * m + 3 * n opérations, et légèrement plus rapide solution a 4 * m.

(Comptage de la boucle "i++" et "i < un.longueur" comme une opération de chaque).

-Al.

1voto

Edwin Buck Points 33097

En supposant qu'un élément a été ajouté, et les tableaux ont été identiques pour commencer, vous pouvez aller en O(log(base 2) n).

La raison en est que toute matrice est l'objet de la recherche binaire-ly O(log n). Sauf que dans ce cas, vous n'êtes pas à la recherche d'une valeur dans un tableau ordonné, vous êtes à la recherche pour le premier non-élément correspondant. Dans ces circonstances, un[n] == b[n] signifie que vous êtes trop faible, et un[n] != b[n] signifie que vous pourriez être trop élevé, à moins qu'un[n-1] == b[n-1].

Le reste est à la base binaire de recherche. Vérifiez le milieu de l'élément, décidez de la division doit avoir la réponse, et de faire un sous-recherche sur cette division.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X