web-dev-qa-db-ja.com

xorが最大の配列内の2つの要素

整数の配列が与えられた場合、XORが最大である2つの要素を見つける必要があります。

素朴なアプローチがあります-各要素を選択して他の要素とxoringし、結果を比較してペアを見つけるだけです。

これ以外に、効率的なアルゴリズムはありますか?

42
Anil Kumar Arya

これにはO(n lg U)アルゴリズムがあると思います。ここで、Uは最大数です。このアイデアはuser949300に似ていますが、もう少し詳細があります。

直感は次のとおりです。 2つの数値をXOR演算して最大値を取得するには、可能な限り高い位置に1を配置し、この位置に1が配置されているペアのうち、次の位置に1とペアを配置する必要があります可能な最高位など.

そのため、アルゴリズムは次のようになります。番号のどこかで最上位の1ビットを見つけることから始めます(n(番号)ごとにO(lg U)の作業を行うことで、O(n lg U)の時間内にこれを行うことができます)。ここで、配列を2つの部分に分割します。1つはそのビットに1があり、グループはそのビットに0があります。最適なソリューションは、最初のスポットの1の数字とそのスポットの0の数字を結合する必要があります。これにより、可能な限り1ビット高くなります。他のペアリングには0があります。

ここで、再帰的に、最も高い1を持つ1と0のグループから番号のペアを見つけたいと思います。これを行うには、これら2つのグループのうち、それらを4つのグループに分割します。

  • 11で始まる番号
  • 10で始まる番号
  • 01で始まる番号
  • 00で始まる番号

11と00のグループ、または10と01のグループに番号がある場合、それらのXORが理想的です(11から始まる)。したがって、これらのグループのペアのいずれかが '空の場合、これらのグループから理想的な解を再帰的に計算し、それらのサブ問題の解の最大値を返します。 XOR 1で始まる数値と0で始まる数値の場合、次の2番目のビットがキャンセルされるため、3番目のビットだけを見る必要があります。

これにより、MSBによって分割された2つのグループの数値から始めて、次の再帰アルゴリズムが得られます。

  • グループ1とグループ0およびビットインデックスi:が与えられた場合
    • ビットインデックスがビット数と等しい場合、1グループの(一意の)番号と0グループの(一意の)番号のXORを返します。
    • それらのグループからグループ11、10、01、および00を作成します。
    • グループ11とグループ00が空でない場合、ビットi + 1から始まるこれら2つのグループの最大XORを再帰的に見つけます。
    • グループ10およびグループ01が空でない場合、ビットi + 1から始まるこれら2つのグループの最大XOR.
    • 上記のいずれかの組み合わせが可能であった場合、再帰によって見つかった最大のペアを返します。
    • それ以外の場合、すべての数値は位置iに同じビットを持っている必要があるため、グループ1と0のビットi + 1を調べて見つかった最大ペアを返します.

アルゴリズムを開始するには、実際には、最初のグループの番号を2つのグループ(MSB 1の番号とMSB 0の番号)に分割します。次に、2つの番号グループで上記のアルゴリズムの再帰呼び出しを実行します。

例として、5 1 4 3 0 2の数字を考えます。これらには表現があります

101  001  100   011   000   010

それらを1グループと0グループに分割することから始めます。

101  100
001  011  000  010

次に、上記のアルゴリズムを適用します。これをグループ11、10、01、および00に分割します。

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

現在、11個の要素と00個の要素をペアにすることはできないため、10個と01個のグループで再帰します。これは、100、101、010、および011グループを構築することを意味します。

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

要素が1つだけのバケットになったので、101と010(111を与える)と100と011(111を与える)のペアをチェックするだけです。どちらのオプションもここで機能するため、最適な答えは7です。

このアルゴリズムの実行時間について考えてみましょう。数値にはO(log U)ビットしかないため、最大再帰深度はO(lg U)であることに注意してください。ツリーの各レベルで、各番号は1回の再帰呼び出しで表示されます。各再帰呼び出しは、ビットごとに配布する必要があるため、0グループと1グループの合計数に比例して機能します。したがって、再帰ツリーにはO(log U)レベルがあり、各レベルはO(n) workを実行し、O(n log U)の合計作業量を与えます。

お役に立てれば!これはすごい問題でした!

42
templatetypedef

これは、 Trie を使用してO(NlogN)時間の複雑さで解決できます。

  • トライを構築します。整数キーごとに、トライの各ノードは最上位ビットから始まるすべてのビット(0または1)を保持します。
  • ここで、_arr[i]_ の各_arr[0, 1, ..... N]_要素について
    • _arr[i]_に可能な最大xor値を取得するクエリを実行します。異なるタイプのビット(_0 ^ 1_または_1 ^ 0_)のxorは常に_1_です。したがって、各ビットのクエリ中に、反対のビットを保持しているノードをトラバースしてみてください。これにより、特定のビット_1_がxor値を最大化します。反対ビットのノードがない場合は、同じビットノードを横断するだけです。
    • クエリの後、_arr[i]_をトライに挿入します。
    • 各要素について、可能な最大Xor値を追跡します。
    • 各ノードをウォークスルーするときに、Xorが最大化される他のキーを構築します。

N要素の場合、各要素に対して1つのquery(O(logN))と1つのinsert(O(logN))が必要です。したがって、全体的な時間の複雑さはO(NlogN)です。

それがどのように機能するかについての素敵な絵の説明を見つけることができます このスレッドで .

上記のアルゴリズムのC++実装は次のとおりです。

_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

符号ビットを無視すると、値の1つは最上位ビットが設定された値の1つでなければなりません。 すべての値にそのビットが設定されていない限り。この場合、すべての値に設定されていない次の最上位ビットに移動します。したがって、HSBを調べることにより、最初の値の可能性を削減できます。たとえば、可能性がある場合

0x100000
0x100ABC
0x001ABC
0x000ABC

最大ペアの最初の値は0x100000または0x10ABCDでなければなりません。

@internal Server Error最小のものが必ずしも正しいとは思わない。 2番目の値を削減するための素晴らしいアイデアはありません。可能な最初の値のリストにあるではないの値。私の例では、0x001ABCまたは0x000ABCです。

4
user949300

非常に興味深い問題です!私のアイデアは次のとおりです。

  • まず、バイナリ表現を使用してすべての数値からバイナリツリーを構築し、それらをツリーの最上位ビットに最初にソートします(最も長い数値に一致するように先行ゼロを追加します)。完了すると、ルートからリーフへの各パスは、元のセットの1つの番号を表します。
  • Aとbをツリーノードへのポインタとし、ルートで初期化します。
  • 次に、各ステップで反対側のエッジを使用して、aとbをツリー内で下に移動します。つまり、aが0エッジを下に移動する場合、bは1エッジを下に移動します。

Aとbがリーフに到達した場合、「ごく少数」の同一ビットを持つ2つの数値を指す必要があります。

私はこのアルゴリズムを作成したばかりで、そのアルゴリズムが正しいのか、それをどのように証明するのかわかりません。ただし、O(n)実行時間である必要があります。

3
NiklasMM

引数として整数の2つのリストAとBをとる再帰関数を作成します。戻り値として、Aから1つ、Bから1つの2つの整数を返します。これにより、2つのうちXOR。が最大になります。すべての整数が0の場合、(0,0)関数は何らかの処理を行い、それ自体を2回再帰的に呼び出しますが、より小さい整数を使用します。再帰呼び出しの1つでは、リストAから整数を取得して1からビットkを提供することを検討します。リストBから1をビットkに提供します。

詳細を記入する時間がありませんが、答えを見るにはこれで十分でしょうか?また、実行時間がN ^ 2よりも良いかどうかはわかりませんが、おそらくそうです。

1
David Grayson