web-dev-qa-db-ja.com

XOR操作の直観

私は最近Leetcodeに関する this の質問に出会い、いくつかの明確化が必要な解決策を見つけました:

整数の配列を指定すると、1つを除くすべての要素が2回出現します。その1つを見つけます。

注:アルゴリズムの実行時の複雑さは線形でなければなりません。余分なメモリを使用せずに実装できますか?

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int result = 0;
        for(auto & c : nums) {
            result ^= c;
        }
        return result;
    }
};

まず、この質問にXOR操作を使用する必要があることを理解するために、どのようなキーワードに注意を払う必要がありますか?

また、ベクター内のすべてのアイテムを互いにXORすると、繰り返されないアイテムが得られるのはなぜですか?


これらの応答に感謝します。興味のある人のためのビット単位のプロパティに関する詳細情報を以下に示します。 ビット単位の詳細

35
User 5842
  1. A ^ 0 == A

  2. A ^ A == 0

  3. A ^ B == B ^ A

  4. (A ^ B) ^ C == A ^ (B ^ C)

(3)と(4)を一緒にすると、数字がxoredになる順序は関係ありません。

つまり、たとえば、A^B^X^C^B^A^CA^A ^ B^B ^ C^C ^ Xと等しくなります。

(2)は0^0^0^Xと等しいためです。

(1)はXと等しいためです。


このような問題を特定するのに役立つ特定のキーワードはないと思います。 XORの上記のプロパティを知っている必要があります。

87
HolyBlackCat

Xor演算子はcommutative

1.      X ⊕ Y = Y ⊕ X                    for any integers X and Y

および結合

2.      X ⊕ (Y ⊕ Z) = (X ⊕ Y) ⊕ Z      for any integers X, Y and Z

したがって、xor操作のシーケンスの結果は、オペランドの順序(つまり、配列内の要素の順序)から完全に独立しています。

3.     X ⊕ X = 0                         for any integer X

4.     X ⊕ 0 = 0 ⊕ X = X                for any integer X

問題には、各要素Aiが2つの特異な要素Bを除いて2回現れる式があります。結果のXor演算は次と同等です。

     (A1 ⊕ A1) ⊕ (A2 ⊕ A2) ⊕    ...   ⊕ B
 = 
         0      ⊕      0     ⊕    ...   ⊕ B
 = 
         B

この質問にXOR操作を使用する必要があることを理解するために、どのようなキーワードに注意を払う必要がありますか

一部の問題は、ビット操作を使用して迅速に解決できます。ブール演算子とそのプロパティに精通し、このようなアプリケーションを十分に見た後、特定の問題を解決するのにそれらが有用であるかどうかを自然に「感じる」でしょう。

22
A.S.H

[〜#〜] xor [〜#〜]を他の論理演算子と区別する重要な直感的な側面は、 lossless 、または non-lossy 、つまり、[〜#〜] and [〜#〜]、および[〜# 〜] or [〜#〜](およびこの点で[〜#〜] not [〜#〜]に類似)、それは決定論的に可逆です。計算履歴の残りを与えられた入力値。

次の図は、[〜#〜] and [〜#〜]および[〜#〜] or [〜#〜]がそれぞれ少なくとも1つのケースを持つことを示しています。他の入力の特定の値が与えられると、入力の1つの状態は回復不能です。これらを「失われた」入力として示します。

AND and OR logical operators can lose information

[〜#〜] xor [〜#〜]ゲートの場合、残りの計算履歴を考慮して、入力値または出力値を回復できない状態はありません。実際、トリプル(in0, in1, out)任意の2つの値を知っていると、3番目の値を回復できるという対称性があります。つまり、入力または出力に関係なく、これら3つの値はそれぞれ、他の2つの[〜#〜] xor [〜#〜]です。

XOR logical operator does not lose information

この図は、[〜#〜] xor [〜#〜]操作を考える別の方法が制御可能なNOTゲートとしてであることを示唆しています。入力の1つ(上記の例では上の入力)を切り替えることにより、他の(下の)入力を無効にするかどうかを制御できます。

[〜#〜] xor [〜#〜]は、 positive-logicnot-equals( ≠)2つの入力に関する機能。したがって、equals function(=)under negative-logic .

その対称性と情報保存特性に従って、可逆性または完全なデータ回復を必要とする問題については、[〜#〜] xor [〜#〜]が思い浮かぶはずです。最も明らかな例は、[〜#〜] xor [〜#〜]定数 'キー'を使用してデータセットを簡単に隠蔽することで、キーを知っている( "秘密" )、正確な回復を可能にします。

hashing でも利用可能な情報をすべて保持することが望ましいです。ソースアイテムを最大限に区別するハッシュ値が必要なため、できる限り多くの特徴的な特性がハッシュコードに組み込まれ、損失が最小限に抑えられるようにする必要があります。たとえば、64ビット値を32ビットにハッシュするには、プログラミング言語[〜#〜] xor [〜#〜] operator ^を使用します。 64入力ビットのそれぞれが出力に影響を与える機会があることを保証します。

uint GetHashCode(ulong ul)
{
    return (uint)ul ^ (uint)(ul >> 32); 
}

この例では、[〜#〜] xor [〜#〜]が使用されていても情報が失われることに注意してください。 (実際、「戦略的情報の損失」はハッシュの全体的なポイントの一種です)。 ulの元の値はハッシュコードから回復できません。その値だけでは、内部計算で使用された3つの32ビット値のうち2つが存在しないためです。完全に反転するには、3つの値のうち2つを保持する必要があることを思い出してください。結果のハッシュコードと[〜#〜] xor [〜#〜] edであった2つの値から、結果を保存した可能性がありますが、通常は後者のどちらも使用するために保存しません他を取得するためのキー値として。1

面白いことに、[〜#〜] xor [〜#〜]bit-twiddling hacks の時代に非常に役立ちました。当時の私の貢献は、C/C++で分岐せずに条件付きでビットを設定またはクリアする方法でした。

unsigned int v;       // the value to modify
unsigned int m;       // mask: the bits to set or clear
int f;                // condition: 0 to 'set', or 1 to 'clear'

v ^= (-f ^ v) & m;    // if (f) v |= m; else v &= ~m;

より深刻な注意点として、[〜#〜] xor [〜#〜]が非可逆であるという事実は、未来のコンピューティングにとって重要な 情報理論的な 意味を持っています。 、情報処理と熱力学の 第二法則 の重要な関係のため。 Charles Seifeによる優れたアクセス可能な本 Decoding the Universe で説明されているように、計算中の情報の損失には e̲x̲a̲c̲t̲ 処理システムから放射される 黒体放射 との数学的な関係。実際、 エントロピー の概念は、情報「損失」が熱として(再)表現される方法を定量化する上で中心的な役割を果たします(これはスティーブン・ホーキングの有名な ブラックホールの賭け )。

[〜#〜] xor [〜#〜]に関するこのような話は必ずしもストレッチではありません。 Seife氏は、現在のCPU開発は現在、半導体材料のwatts /cm²に対する基本的な許容制限に直面しており、ソリューションは可逆的または無損失のコンピューティングシステムを設計することであると述べています。この投機的な将来世代のCPUでは、[〜#〜] xor [〜#〜]の情報を保持する機能— により、熱を逃がします -そのような材料の制限にもかかわらず、計算密度を高めるために非常に貴重です(つまり、 [〜#〜] mips [〜#〜] /cm²)。



1.この単純な例では、関連する3つの値は、ハッシュコードと元のulong値の上位部分と下位部分になります。もちろん、ここでulで表される元のハッシュされた「データ」自体は、おそらく is が保持されます。
9
Glenn Slayden

ご存じのとおり、整数は2進数のタプルとしてメモリに格納されます。各桁は、2つの要素のフィールドの数値として見ることができます。これは基本的に2を法とする整数です。^演算子はコンポーネントごとのxorであり、この解釈ではxorは単純にadditionです。つまり、バイナリdigitsを互いに追加しています。

このフィールドでは、1 + 1 = 0であるため、2はゼロであると言えます。加算は可換および結合なので、すべての数値を一度に結合できます。偶数回追加されたものは何も追加せず、1回追加された数だけが結果変数になります。

ブール演算が次のように表現されていることを知るのは興味深いかもしれません:(試してみてください!)a xor b = a + b、a and b = ab、a または b = ab + a + b、not a = a + 1。

0
Aksel Bergfeldt

配列に対して実行できる簡単な操作の1つは、プロパティPを選択し、プロパティを満たす配列の要素の数をカウントすることです。たとえば、Pが5で割り切れるプロパティである場合、配列をループして、5で割り切れる要素の数をカウントできます。

配列の前提条件により、このようなカウントからシングルトン要素に関する情報を取得できます。 Pを満たす要素の数が奇数の場合、シングルトンにはプロパティPがあります。 Pを満たす要素の数が偶数の場合、シングルトンにプロパティPがあってはなりません。たとえば、5で割り切れる3つの要素をカウントする場合、シングルトンは5で割り切れる必要があります。

したがって、各プロパティがtrueまたはfalseであるかを知ることで要素を完全に指定できるようにプロパティのコレクションを作成できる場合、各プロパティで要素の数をカウントし、カウントのパリティをチェックすることで答えを得ることができます。

機能するプロパティの多くの異なるコレクションがあります。ただし、効率的な場合もあります。 bのさまざまなプロパティを指定すると、テストは(最大で)2**bさまざまな方法で実行できるため、この多くの可能な要素からのみ区別できます。したがって、b-ビット数を完全に指定するには、少なくともb異なるプロパティが必要です。

コンピューターがテストするのが簡単なプロパティの最小コレクションの1つは、nthプロパティが「番号のnthビットに1があるか?」というコレクションです。 nごとにこの質問に対する答えがわかっていれば、明らかに数字を再構築できます。

もう1つの最適化は、プロパティを満たす要素の総数を追跡する必要がないことです。パリティだけが必要です。 2を法とするカウントを追跡するだけの場合、size_t全体ではなく、1ビットだけで追跡することができます。

特定の場所の値bに対応するnの異なるビットの情報を追跡しているため、これらのビットをまとめてb-ビット数にまとめることができます。

これは、質問で提示されたXORソリューションです。

そもそも、各ビットの数の実行カウントは、各ビットで0です(したがって、resultは0に初期化されます)。次に、XOR=配列の要素)の場合、実際には要素が1であるresultのビットに2を法とする1を追加します。最後に、resultresultが1ビットである正確に1ビットの数はresultであるため、デコードは必要ありません。

0
tehtmi