web-dev-qa-db-fra.com

Deux éléments dans un tableau dont le xor est maximum

Étant donné un tableau d'entiers, vous devez trouver deux éléments dont XOR est maximum.

Il existe une approche naïve - il suffit de choisir chaque élément et de le xorer avec d'autres éléments, puis de comparer les résultats pour trouver la paire.

À part cela, existe-t-il un algorithme efficace?

42
Anil Kumar Arya

Je pense que j'ai un algorithme O (n lg U) pour cela, où U est le plus grand nombre. L'idée est similaire à celle de user949300, mais avec un peu plus de détails.

L'intuition est la suivante. Lorsque vous effectuez XOR deux nombres ensemble, pour obtenir la valeur maximale, vous voulez avoir un 1 à la position la plus élevée possible, puis des appariements qui ont un 1 à cette position, vous voulez un appariement avec un 1 à la prochaine position la plus élevée possible, etc.

Donc, l'algorithme est le suivant. Commencez par trouver le bit le plus élevé n'importe où dans les nombres (vous pouvez le faire dans le temps O (n lg U) en faisant O (lg U) pour chacun des n nombres). Maintenant, divisez le tableau en deux parties - l'un des nombres qui ont un 1 dans ce bit et le groupe avec 0 dans ce bit. Toute solution optimale doit combiner un nombre avec un 1 au premier endroit avec un nombre avec un 0 à cet endroit, car cela mettrait un bit aussi haut que possible. Tout autre appariement contient un 0.

Maintenant, récursivement, nous voulons trouver l'appariement des nombres du groupe 1 et 0 qui a le 1 le plus élevé en eux. Pour ce faire, de ces deux groupes, divisez-les en quatre groupes:

  • Nombres commençant par 11
  • Nombres commençant par 10
  • Numéros commençant par 01
  • Numéros commençant par 00

S'il y a des nombres dans le groupe 11 et 00 ou dans les groupes 10 et 01, leur XOR serait idéal (commençant par 11). Par conséquent, si l'une de ces paires de groupes n'est pas ' t vide, calculez récursivement la solution idéale à partir de ces groupes, puis renvoyez le maximum de ces solutions de sous-problème. Sinon, si les deux groupes sont vides, cela signifie que tous les nombres doivent avoir le même chiffre dans leur deuxième position. Par conséquent, l'optimal = XOR d'un nombre commençant par 1 et d'un nombre commençant par 0 finira par annuler le deuxième bit suivant, nous devons donc simplement regarder le troisième bit.

Cela donne l'algorithme récursif suivant qui, en commençant par les deux groupes de nombres partitionnés par leur MSB, donne la réponse:

  • Étant donné le groupe 1 et le groupe 0 et un indice binaire i:
    • Si l'index de bits est égal au nombre de bits, renvoyez le XOR du nombre (unique) dans le groupe 1 et le nombre (unique) dans le groupe 0.
    • Construisez les groupes 11, 10, 01 et 00 à partir de ces groupes.
    • Si le groupe 11 et le groupe 00 ne sont pas vides, trouvez récursivement le maximum XOR de ces deux groupes commençant au bit i + 1.
    • Si le groupe 10 et le groupe 01 ne sont pas vides, trouvez récursivement le maximum XOR de ces deux groupes, en commençant au bit i + 1.
    • Si l'un des appariements ci-dessus était possible, renvoyez la paire maximale trouvée par la récursivité.
    • Sinon, tous les nombres doivent avoir le même bit en position i, donc renvoyez la paire maximale trouvée en regardant le bit i + 1 sur les groupes 1 et 0.

Pour démarrer l'algorithme, vous pouvez en fait simplement partitionner les nombres du groupe initial en deux groupes - les nombres avec MSB 1 et les nombres avec MSB 0. Vous lancez ensuite un appel récursif à l'algorithme ci-dessus avec les deux groupes de nombres.

Par exemple, considérons les nombres 5 1 4 3 0 2. Ils ont des représentations

101  001  100   011   000   010

Nous commençons par les diviser en groupe 1 et groupe 0:

101  100
001  011  000  010

Maintenant, nous appliquons l'algorithme ci-dessus. Nous avons divisé cela en groupes 11, 10, 01 et 00:

11:
10:  101  100
01:  011  010
00:  000  001

Maintenant, nous ne pouvons pas associer 11 éléments à 00 éléments, donc nous récurrons simplement sur les groupes 10 et 01. Cela signifie que nous construisons les groupes 100, 101, 010 et 011:

101: 101
100: 100
011: 011
010: 010

Maintenant que nous en sommes aux seaux contenant un seul élément, nous pouvons simplement vérifier les paires 101 et 010 (ce qui donne 111) et 100 et 011 (ce qui donne 111). Chaque option fonctionne ici, donc nous obtenons que la réponse optimale est 7.

Pensons au temps d'exécution de cet algorithme. Notez que la profondeur de récursivité maximale est O (lg U), car il n'y a que O (log U) bits dans les nombres. À chaque niveau de l'arborescence, chaque numéro apparaît dans exactement un appel récursif, et chacun des appels récursifs fonctionne proportionnellement au nombre total de nombres dans les groupes 0 et 1, car nous devons les répartir par leurs bits. Par conséquent, il existe O (log U) niveaux dans l'arbre de récursivité, et chaque niveau fonctionne O(n)), ce qui donne un travail total de O (n log U).

J'espère que cela t'aides! Ce fut un problème génial!

42
templatetypedef

Cela peut être résolu en O(NlogN) complexité temporelle en utilisant Trie .

  • Construisez un trie. Pour chaque clé entière, chaque nœud du trie contiendra chaque bit (0 ou 1) à partir du bit le plus significatif.
  • Maintenant, pour chaque élément arr[i] De arr[0, 1, ..... N]
    • Exécutez une requête pour récupérer la valeur xor maximale possible pour arr[i]. Nous savons que xor de différents types de bits (0 ^ 1 Ou 1 ^ 0) Est toujours 1. Donc, pendant la requête pour chaque bit, essayez de traverser le nœud contenant le bit opposé. Cela fera que ce bit particulier 1 Aboutira à maximiser la valeur xor. S'il n'y a pas de nœud avec le bit opposé, alors traversez le même nœud de bit.
    • Après la requête, insérez arr[i] Dans le trie.
    • Pour chaque élément, gardez une trace de la valeur Xor maximale possible.
    • En parcourant chaque nœud, créez l'autre clé pour laquelle le Xor est maximisé.

Pour les éléments N, nous avons besoin d'une requête (O(logN)) et d'une insertion (O(logN)) pour chaque élément. La complexité temporelle globale est donc O(NlogN).

Vous pouvez trouver une belle explication picturale sur la façon dont cela fonctionne dans ce fil .

Voici l'implémentation C++ de l'algorithme ci-dessus:

const static int SIZE = 2;
const static int MSB = 30;
class trie {
private:
    struct trieNode {
        trieNode* children[SIZE];
        trieNode() {
            for(int i = 0; i < SIZE; ++i) {
                children[i] = nullptr;
            }
        }
        ~trieNode() {
            for(int i = 0; i < SIZE; ++i) {
                delete children[i];
                children[i] = nullptr;
            }
        }
    };
    trieNode* root;
public:
    trie(): root(new trieNode()) {
    }
    ~trie() {
        delete root;
        root = nullptr;
    }

    void insert(int key) {
        trieNode* pCrawl = root;
        for(int i = MSB; i >= 0; --i) {
            bool bit = (bool)(key & (1 << i));
            if(!pCrawl->children[bit]) {
                pCrawl->children[bit] = new trieNode();
            }
            pCrawl = pCrawl->children[bit];
        }
    }

    int query(int key, int& otherKey) {
        int Xor = 0;
        trieNode *pCrawl = root;
        for(int i = MSB; i >= 0; --i) {
            bool bit = (bool)(key & (1 << i));
            if(pCrawl->children[!bit]) {
                pCrawl = pCrawl->children[!bit];
                Xor |= (1 << i);
                if(!bit) {
                    otherKey |= (1 << i); 
                } else {
                    otherKey &= ~(1 << i);
                }
            } else {
                if(bit) {
                    otherKey |= (1 << i); 
                } else {
                    otherKey &= ~(1 << i);
                }
                pCrawl = pCrawl->children[bit];
            }
        }
        return Xor;
    }
};

pair<int, int> findMaximumXorElements(vector<int>& arr) {
    int n = arr.size();
    int maxXor = 0;
    pair<int, int> result; 
    if(n < 2) return result;
    trie* Trie = new trie();
    Trie->insert(0); // insert 0 initially otherwise first query won't find node to traverse
    for(int i = 0; i < n; i++) {
        int elem = 0;
        int curr = Trie->query(arr[i], elem);
        if(curr > maxXor) {
            maxXor = curr;
            result = {arr[i], elem};
        }
        Trie->insert(arr[i]);
    }
    delete Trie;
    return result;
}
4
Kaidul

En ignorant le bit de signe, l'une des valeurs doit être l'une des valeurs avec le bit de poids fort le plus élevé. Sauf si toutes les valeurs ont ce bit défini, auquel cas vous passez au bit significatif le plus élevé suivant qui n'est pas défini dans toutes les valeurs. Vous pouvez donc réduire les possibilités pour la 1ère valeur en regardant le HSB. Par exemple, si les possibilités sont

0x100000
0x100ABC
0x001ABC
0x000ABC

La 1ère valeur de la paire max doit être 0x100000 ou 0x10ABCD.

@internal Server Error Je ne pense pas que le plus petit soit nécessairement correct. Je n'ai pas une bonne idée pour réduire la 2ème valeur. Juste n'importe quelle valeur qui n'est pas dans la liste des premières valeurs possibles. Dans mon exemple, 0x001ABC ou 0x000ABC.

4
user949300

Un problème très intéressant! Voici mon idée:

  • Construisez d'abord un arbre binaire à partir de tous les nombres en utilisant la représentation binaire et triez-les d'abord dans le bit le plus significatif de l'arbre (ajoutez des zéros de tête pour correspondre au nombre le plus long). Une fois terminé, chaque chemin de la racine à n'importe quelle feuille représente un numéro de l'ensemble d'origine.
  • Soit a et b des pointeurs vers un nœud d'arbre et initialisez-les à la racine.
  • Maintenant, déplacez a et b vers le bas de l'arborescence, en essayant d'utiliser des bords opposés à chaque étape, c'est-à-dire que si a descend un bord 0, b descend un bord 1 sauf si ce n'est pas possible.

Si a et b atteignent une feuille, le doit pointer vers deux nombres avec "très peu" de bits identiques.

Je viens de créer cet algorithme et je ne sais pas si c'est correct ou comment le prouver. Cependant, il devrait être dans le temps d'exécution O(n).

3
NiklasMM

Créez une fonction récursive qui prend deux listes d'entiers, A et B, comme arguments. En tant que valeur de retour, il renvoie deux entiers, un de A et un de B, qui maximisent le XOR des deux. Si tous les entiers sont 0, retourne (0,0). Sinon , la fonction effectue un certain traitement et s'appelle récursivement deux fois, mais avec des entiers plus petits. Dans l'un des appels récursifs, elle envisage de prendre un entier de la liste A pour fournir un 1 au bit k, et dans l'autre appel, elle envisage de prendre un entier de la liste B pour fournir un 1 au bit k.

Je n'ai pas le temps maintenant de remplir les détails, mais peut-être que ce sera suffisant pour voir la réponse? De plus, je ne sais pas si le temps d'exécution sera meilleur que N ^ 2, mais ce sera probablement le cas.

1
David Grayson