*もちろん意識的に多くの知識を学びましたが、実際に問題を解き始めると、「ペン、両手、一晩リートコード」というジレンマがまだありました。問題形式はそれほど難しくなく、解答を読めば解けるのに、自分でやろうとすると問題を直視してしまい、始まらない、そんな経験はありませんか。これは、基礎知識がしっかりと理解されていないことが原因であり、ビット演算を例に挙げて、ビット演算の基礎をわかりやすく説明し、例を挙げて実際に体験していただきます。
1. ビット単位の AND 演算子
ビット単位の AND 演算子は 2 項のビット単位演算子です。つまり、x と y で示される 2 つのオペランドがあります。
ビット単位の AND 演算は、次の表に従ってオペランドの各ビットに対して演算されます。各ビットには 0 または 1 の 2 つのケースしかなく、合計 4 つのケースが結合されます。
この表から、いくつかの結論を導き出します。
1) ビットが 1 であるか、それ自体である限り、ビットが 0 か 1 か。
2) 0 であっても 1 であっても、ビットが 0 であれば 0 になります。
したがって、ビットごとの AND の場合、このビットに 0 がある限り、このビットの結果は 0 になります。
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a & b) );
return 0;
}
-
(1) C 言語では、2 進数であることを示す接頭辞として 0b が使用されます。この場合、a の実際の値は (1010) になります。
-
(2) 同様に、b の実際の値は (0110) です。
-
(3) ここで、a & b は、(1010) と (0110) の各桁に対する表の & 演算です。
-
最終的な出力は次のようになります。
-
出力は 10 進数であるため、2 進数表現は (0010)_2 となります。
-
注:ここでの先頭のゼロはオプションです。作成者は、位置調整のためと読者にビットごとの AND 演算をより認識してもらうために、先頭のゼロを記述しています。
2. ビットごとの AND 演算子の適用
1. パリティ判定
- 数値が奇数か偶数かを判断するには、多くの場合、次のように係数 % を使用して判断します。
int main() {
if(5 % 2 == 1) {
printf("5是奇数\n");
}
if(6 % 2 == 0) {
printf("6是偶数\n");
}
return 0;
}
- ただし、次のように書くこともできます。
int main() {
if(5 & 1) {
printf("5是奇数\n");
}
if( (6 & 1) == 0 ) {
printf("6是偶数\n");
}
return 0;
}
1.
- これは、次の表に示すように、奇数と偶数の 2 進数の特性を利用したものです。
-
したがって、任意の数値に対して 0b1 とのビット AND を実行し、結果が 0 の場合、その数値の 2 進数の終了ビットは 0 でなければなりません。上の表によれば、それは偶数であると結論付けることができます。それ以外の場合は奇数です。
-
if ステートメントについては実際には言及していないため、ここでは簡単に説明し、体系的な説明は後で行うことに注意してください。
-
上記のステートメントでは、 expr は式を表し、式の値は末尾の 0 または 0 以外のみで、値が 0 以外の場合は本文の内容が実行されます。
2. 最後の 5 桁を取得します
[例 1] 数値が与えられた場合、その 2 進数表現の下 5 桁を求め、10 進数で出力します。
-
この問題の核心は、最後の 5 桁だけが必要で、残りの桁は必要ないため、指定された桁を 0b11111 に追加して、最後の 5 桁の値を直接取得できることです。
-
コードは次のように実装されます。
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0b11111) );
return 0;
}
では、問題が次のような形になった場合、どのようにコードを書けばよいのでしょうか?
【例2】下7桁、下9桁、下14桁、下K桁を取得したい場合、どうすればよいでしょうか?
3. 下5桁を削除する
【例 3】32 ビット整数が与えられた場合、その下 5 桁を削除する必要があります。
-
ビットの性質に従って、最後の 5 ビットの意味を除外すると、次の 2 つの層があります。
-
1) 最後の 5 桁はすべてゼロになる必要があります。
-
2) 残りのビットは変更されません。
-
次に、ビット演算の性質に従って、上位 27 ビットがすべて 1、下位 5 ビットがすべて 0 である数値が必要になります。この場合、この数値は次のようになります。
-
しかし、このように書きたい場合は、コードはおかしくなりませんし、人々はおかしくなります。そのため、一般的にはそれを 16 進数に変換し、2 進数の 4 桁ごとに 16 進数に変換できるため、16 進数の数値が得られます。は0xffffffe0です。
-
コードは次のように実装されます。
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0xffffffe0) );
return 0;
}
ヒント: f は 4 つの 1 を表し、e は 3 つの 1 と 1 つの 0 を表し、0 は 4 つの 0 を表します。
4.末尾の連続する1を消す
[例4] 整数が与えられた場合、その整数を2進数に変換し、末尾に連続する1を0に変更して出力する必要があります(10進数で出力するだけ)。
- この数値のバイナリ表現は次のようになっている必要があることがわかっています。
- この 2 進数に 1 を加えると、次のようになります。
- これら 2 つの数値に対してビット単位の AND 演算を実行して、次の結果を取得します。
- それで、あなたは学びましたか?
5. 2の累乗判定
【例5】正の数が2の累乗であるかどうかを一文で判断してください。
- 数値が 2 の累乗である場合、そのバイナリ表現は次の形式でなければなりません。
-
この数値の 10 進数値は 2 k 倍です。
-
次に、そこから 1 を引きます。つまり、2 から 1 を引いた k 倍のバイナリ表現は次のようになります (2 進減算の借用を参照)。
- したがって、これら 2 つの数値の AND の結果はゼロになるため、数値 x が 2 のべき乗である場合、x & (x-1) はゼロでなければならないことがわかります。他の場合には、これは当てはまりません。したがって、この質問に対する答えは次のとおりです。
3. ビット単位の OR 演算子
ビット単位の OR 演算子は 2 項のビット単位演算子です。つまり、x | y で示される 2 つのオペランドがあります。ビット OR 演算は、次の表に従ってオペランドの各ビットに対して演算されます。各ビットには 0 または 1 の 2 つのケースしかなく、合計 4 つのケースが結合されます。
この表から、いくつかの結論を導き出します。
1) 0 であっても 1 であっても、ビットが 1 であれば 1 になります。
2) 両方のオペランドが 0 の場合のみ 0 になります。
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a | b) );
return 0;
}
(1) C 言語では、2 進数であることを示す接頭辞として 0b が使用されます。この場合、a の実際の値は (1010) になります。
(2) 同様に、b の実際の値は (0110) です。
(3) ここで、a | b は、(1010) と (0110) の各桁に対する表の | 演算です。
最終的な出力は次のようになります。
出力は 10 進数であるため、2 進数表現は (1110) となります。
4 番目、ビットごとの OR 演算子の適用
1. マークビットを設定する
【例1】数値を与えて、その2進数の下位5ビット目を判定し、0であれば1に設定します。
この問題は、ビットまたはを簡単に考えることができます。タイトルの意味を解析してみましょう 5 ビット目が 1 の場合は何も操作する必要はありませんが、5 ビット目が 0 の場合は 1 に設定します。つまり、5 番目のビットが何であっても、それを直接 1 に設定できるということです。コードは次のとおりです。
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x | 0b10000);
return 0;
}
2. マークビットを空にする
【例 2】数値を与えて、その 2 進数の下位 5 ビット目を判定し、1 であれば 0 に設定します。
少し学習すると、次のようなアプローチにたどり着くのは簡単です。
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x & 0b11111111111111111111111111101111);
return 0;
}
他のビットは変更できないため、ビットは 1 にする必要があります。5 番目のビットは 0 に設定する必要があるため、ビットは 0 にする必要があります。この方法で書くと問題があります。つまり、数値の文字列が長すぎます。決して美しくありませんし、間違いやすいです。もちろん、16 進数に変換することもでき、変換プロセスでも間違いが発生する可能性があります。
そして、bit を使用して 5 番目のビットを 1 に設定することしかできません。または、それを 0 に設定するにはどうすればよいでしょうか?
引き算でも使えます。以下の 2 つのステップに分かれています。
1) まず、下位ビットの 5 番目の位置を強制的に 1 に変更します。
2) 次に、下位 5 桁を強制的に削除します。
ステップ (1) ではビット OR 演算を使用でき、ステップ (2) では減算を直接使用できます。コードは次のように実装されます。
int main() {
int x;
int a = 0b10000;
scanf("%d", &x);
printf("%d\n", (x | a) - a );
return 0;
}
注: 直接の減算は受け入れられません。最初にビットが 1 であることを確認する必要があります。そうしないと、無謀な減算によってボローが生成され、質問の意味と矛盾します。
3. 低連続ゼロから 1
【例3】整数xが与えられたとき、その下位に連続する0を1に変更します。
この整数の下位ビットに k 個の連続するゼロがあると仮定すると、バイナリ表現は次のようになります。
したがって、そこから 1 を引くと、結果の 2 進数は次のようになります。
これら 2 つの数値が OR されている限り、次の結果が得られることがわかりました。
まさにタイトルのとおりなので、コードの実装は次のとおりです。
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x | (x-1) );
return 0;
}
(1) x | (x-1) はタイトルで要求された「低連続 0 から 1」です。
4. ローファースト 0 から 1
【例4】整数xが与えられたとき、その下位の最初の0を1に変更します。
コメント欄に答えを忘れずに残してください~
5、XOR演算子
XOR 演算子は 2 項のビット演算子です。つまり、x ^ y で示される 2 つのオペランドがあります。
XOR 演算は、次の表に従ってオペランドの各ビットに対して演算されます。各ビットには 0 または 1 の 2 つのケースしかなく、合計 4 つのケースが結合されます。
この表から、いくつかの結論を導き出します。
1) 2 つの同一の 10 進数の XOR 結果はゼロでなければなりません。
2) 任意の数値と 0 の XOR 結果は、それ自体でなければなりません。
3) XOR 演算は結合法則と交換法則を満たします。
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a ^ b) );
return 0;
}
(1) C 言語では、2 進数であることを示す接頭辞として 0b が使用されます。この場合、a の実際の値は (1010) になります。
(2) 同様に、b の実際の値は (0110) です。
(3) ここで、a ^ b は、(1010) と (0110) の各桁に対する表の ^ 演算です。
最終的な出力は次のようになります。
出力は 10 進数であるため、2 進数表現は (1100) となります。
六、XOR演算子の応用
1. マークビットを反転する
【例1】数値が与えられた場合、下から4桁目を反転すると、0は1になり、1は0になります。
この問題は、XOR を簡単に考えることができます。タイトルの意味を分析してみましょう。4 番目のビットが 1 の場合は 0b1000 と XOR して 0 になり、4 番目のビットが 0 の場合は 0b1000 と XOR して 1 になります。これらはすべて 0b1000 で XOR されており、コードは次のようになります
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x ^ 0b1000);
return 0;
}
2. 変数交換
【例2】2つの数値aとbが与えられたとき、XOR演算を使用してそれらの値を交換します。
これは古いインタビューの質問で、コードは直接与えられています。
int main() {
int a, b;
while (scanf("%d %d", &a, &b) != EOF) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("%d %d\n", a, b);
}
return 0;
}
2 つの文 (1) と (2) を直接見てみましょう。これは b が a ^ b ^ b に等しいことに相当します。XOR のいくつかの性質によれば、このときの b の値は、元の値になっていることがわかります。アップ。
そして、文 (3) をもう一度見てください。これは、a が a ^ b ^ a に等しい、または XOR のいくつかの性質に従って、この時点で a の値が b の元の値になっています。
これにより、変数aと変数bの交換が実現される。
3. 奇数回出現する数字
【例3】n個の数字を入力します。そのうちの1つだけが奇数回出現し、他の数字はすべて偶数回出現します。奇数回出現する数を求めます。
XOR の性質により、2 つの同一の数値の XOR の結果は 0 になります。つまり、偶数回出現する XOR 数値はすべて 0 であり、n 個の数値すべての XOR を計算すると、得られた数値は奇数回出現する数値になる必要があります。
int main() {
int n, x, i, ans;
scanf("%d", &n);
ans = 0;
for(i = 0; i < n; ++i) {
scanf("%d", &x);
ans = (ans ^ x);
}
printf("%d\n", ans);
return 0;
}
4.紛失した番号
【例4】1からnまでのn-1を表すn-1個の数が与えられたとき、足りない数を求めます。
コメント欄に答えを忘れずに残してください~
5. 簡易暗号化
2 つの数値の XOR は 0 であること、および任意の数値と 0 はそれ自身であるという2 つの特性に基づいて、XOR は単純な暗号化にも使用できます。平文上の固定数値を暗号文に XOR 演算した後、この数値を XOR 演算し続けることで、暗号文を平文に変換できます。
7、ビットごとの否定演算子
否定演算子は単項ビット演算子です。つまり、~x で示されるオペランドが 1 つだけあります。否定演算は、次の表に従ってオペランドの各ビットに対して実行され、各ビットに 0 または 1 の 2 つのケースのみが存在します。
int main() {
int a = 0b1;
printf("%d\n", ~a );
return 0;
}
ここで ~a は 2 進数 1 の反転を表しており、直感的には 0 であるはずです。しかし、実際の出力は次のようになります。
-2
-
どうしてこれなの?これは 32 ビット整数であり、実際の反転演算は次のとおりであるためです。
- 00000000 00000000 00000000 00000001
11111111 11111111 11111111 11111110
32 ビット整数のバイナリ表現。先頭のゼロも否定に含まれます。符号付き 32 ビット整数の場合、符号ビットを表すために最上位ビットを使用する必要があります。つまり、最上位ビットは 0 で正の数を表し、最上位ビットは 1 で負の数を表します。
このとき、補完コードの概念を導入する必要があります。
1. 補完コードの概念
コンピュータではバイナリコードは補数コードという形で表現されますが、補数コードは次のように定義されます。
正の数の補数はそれ自体であり、符号ビットは 0 です。負の数の補数は、正の数の 2 進ビットの反転に 1 を加えたもので、符号ビットは 1 です。
2. 補数コードの例
補数コードの定義によれば、-2 の補数コードの計算には 2 つの手順が必要です。
-
1) 次のように、2 のバイナリに対してビット単位の反転を実行します。
- 00000000 00000000 00000000 00000010
11111111 11111111 11111111 11111101
2) 次に、次のように 1 を追加します。
11111111 11111111 11111111 11111101
- 00000000 00000000 00000000 00000001
11111111 11111111 11111111 11111110
結果は、冒頭で述べた ~1 の結果とまったく同じです。
3. 補数コードの本当の意味
補数コードの本当の意味は、実際には「補数」という言葉に反映されており、数学では、互いに反対の 2 つの数字を足すと 0 に等しくなりますが、コンピュータでは、反対の 2 つの数字を足すと 0 になります。互いに 2 n 倍に等しい。
つまり、互いに反対の 2 つの数は互いに補い合い、2 の n 倍になります。
32 ビット整数の場合、n = 32、64 ビット整数の場合、n = 64。したがって、補数コードは次のように表すこともできます。
したがって、int 型の場合は次のとおりです。
たった今:
それで、数え始めます...
2^32 = 1 00000000 00000000 00000000 00000000
2^32 - 1 = 11111111 11111111 11111111 11111111
2^32 - 2 = 11111111 11111111 11111111 11111110
-2 のバイナリ表現を詳しく見てみましょう。
8、ビットごとの否定演算子の応用
1、0の否定
【例1】0の否定結果は何でしょうか?
-
まず、元のコードを反転して以下を取得します。
- 00000000 00000000 00000000 00000000
11111111 11111111 11111111 11111111
この質問の議論は終了したところですが、答えは 2^32 - 1 です。しかし、実際に出力してみると、その値は -1 であることがわかります。
何故ですか?
理由は、C言語のintにはunsigned intとsigned intの2種類があり、先ほどのintはsigned intの略です。
1) 符号付き整数
符号付き整数型 signed int の場合、最上位ビットが符号ビットを表すため、値を表現できるのは 31 ビットのみであり、表現できる値の範囲は次のとおりです。
したがって、符号付き整数の場合、出力では次のように %d が使用されます。
int main() {
printf("%d\n", ~0 );
return 0;
}
結果は次のとおりです。
-1
2) 符号なし整数
符号なし整数型 unsigned int の場合、符号ビットは必要ないため、値を表すのに合計 32 ビットがあり、値の範囲は次のとおりです。
符号なし整数の場合、出力では次のように %u が使用されます。
int main() {
printf("%u\n", ~0 );
return 0;
}
結果は次のとおりです。
4294967295
つまり 2^32 - 1 です。
2.反対の数字
【例2】int型の正の数xが与えられたとき、xの反対の数を求めます(注:負号は使用できません)。
ここでは、補数の定義を直接使用できます。正の数 x の場合、その反対の数の補数は、x の 2 進反転に 1 を加えたものになります。つまり、 ~x + 1 です。
int main() {
int x = 18;
printf("%d\n", ~x + 1 );
return 0;
}
操作の結果は次のようになります。
-18
3. 引き算の代わりに
【例3】int型の2つの正の数x、yが与えられたとき、x - yを実現します(マイナス記号は使用できません)。
この問題は比較的単純で、上記の逆数が理解できていれば、実際には x - y は x + (-y) と表現でき、-y は ~y + 1 と表現できるので、引き算 x - y は次のようになります。代わりに x + ~y + 1 を使用してください。
- コードは次のように実装されます。
int main() {
int a = 8;
int b = 17;
printf("%d\n", a + ~b + 1 );
return 0;
}
操作の結果は次のようになります。
-9
4. 足し算の代わりに
【例 4】int 型の 2 つの正の数 x と y が与えられたとき、x + y を実現します (注: プラス記号は使用できません)。
x + y を x - (-y) に変更できます。また、-y は ~y + 1 に置き換えることができます。
したがって、 x + y は x - ~y - 1 となり、プラス記号なしで加算されます。
int main() {
int x = 18;
int y = 7;
printf("%d\n", x - ~y - 1 );
return 0;
}
操作の結果は次のようになります。
25
9、左シフト演算子
1. 左シフトバイナリ形式
左シフト演算子は 2 項のビット演算子です。つまり、x << y で表される 2 つのオペランドがあります。ここで、x と y は両方とも整数です。
x << y は「x を y ビット左にシフトする」と発音されます。ここでのビットはもちろんバイナリ ビットなので、これが意味するのは、まず x をバイナリで表し、次に y ビットだけ左にシフトし、 y 個のゼロを追加します。
例: 2 進数 (10111) の場合、y ビットを左にシフトした結果は次のようになります。
2. 左シフトの実行結果
x << y の実行結果は次と同等です。
次のコード:
int main() {
int x = 3;
int y = 5;
printf("%d\n", x << y);
return 0;
}
出力は次のとおりです。
この左シフト演算子の実際の意味は次のとおりです。
最も一般的に使用されるのは、x = 1、1 << y の場合で、2 の累乗である 2^y を表します。
3. 負の左シフトの実行結果
いわゆる負の数の左シフトは、x << y で、x が負の数の場合、コードは次のようになります。
int main() {
printf("%d\n", -1 << 1);
return 0;
}
その出力は次のとおりです。
同じことが満たされることがわかりました。これは補数によって説明できます。-1 の補数は次のとおりです。
1 ビットを左にシフトすると、最上位の 1 が消え、下位ビットが 0 で埋められ、次のようになります。
そして、これは -2 の補数になります。同様に、1 ビット左にシフトし続けると、次のようになります。
これは -4 の補数などであるため、負の整数の左シフトの結果も満たされます。
- (x << y) と (-x) << y は同等であることが理解できます。
4. 負の数字を左にシフトするとどうなるか
先ほど x < 0 の場合について説明しましたが、y < 0 を試してみるとどうなるでしょうか?
次の式も満たしますか?
それでも満たされる場合は、2 つの整数を左シフトすると小数が生成される可能性があります。
例を見てみましょう:
int main() {
printf("%d\n", 32 << -1);
printf("%d\n", 32 << -2);
printf("%d\n", 32 << -3);
printf("%d\n", 32 << -4);
printf("%d\n", 32 << -5);
printf("%d\n", 32 << -6);
printf("%d\n", 32 << -7);
return 0;
}
正常に実行できますが、結果は期待どおりではないようで、次のような警告が報告されます。
[警告] 左シフト数が負です [-Wshift-count-negative]
実際、エディタでは左シフトの際に負の数は極力使わないようにと指示されていますが、その実行結果はエラーとは言えず、少なくとも例では正しく、結果は表示されません。小数ですが四捨五入されます。
負の数字を左にシフトすると、実際には、対応する正の数字を右にシフトするのと同じ効果が得られます。
5. 左シフト時にオーバーフローが発生するとどうなるか
int 型の数値はすべて 32 ビットであり、最上位ビットが符号ビットを表すことがわかっているため、最上位ビットが 1、2 番目に上位ビットが 0 であると仮定すると、左シフト後の符号ビットは 0 になります。 、どのような問題が発生するでしょうか?
たとえば、-2^31 + 1 のバイナリ表現は次のようになります。最上位ビットと最下位ビットは 1 で、残りは 0 です。
int main() {
int x = 0b10000000000000000000000000000001;
printf("%d\n", x);
return 0;
}
出力は次のとおりです。
では、それを 1 ビット左にシフトすると、結果はどうなるでしょうか?
int main() {
int x = 0b10000000000000000000000000000001;
printf("%d\n", x << 1);
return 0;
}
盲目的に推測してみましょう。最上位ビット 1 が削除され、最下位ビットが 0 で埋められると、結果は 0b10 になるはずです。
実際の出力は次のようになります。
ただし、前述のルールに従えば、答えは次のようになります。
これは補数コードの問題に戻りますが、実際、コンピュータでは int 整数は実際にはリングであり、オーバーフロー後に戻ってきますが、リングの長さは正確に 2^32 なので、-2^32 + 2 = 2 、これは合同の概念に少し似ており、これら 2 つの数値は 2^32 を法として合同です。
10、左シフト演算子の応用
1. モジュロ変換からビット演算へ
数値 y モジュロ x モジュロ 2 の累乗の場合、ビット単位の AND 2^y-1 に変換できます。
つまり、数学的には次のようになります。
コンピューターでは、x & ((1 << y) - 1) という 1 行のコードで表すことができます。
2. タグコードの生成
左シフト演算子を使用してマーキング コードを実現できます。つまり、k 番目のマーキング位置のマーキング コードとして 1 << k が使用され、0、1 の設定、マーキング位置の反転などの操作が可能になります。一文で実現します。
1) 位置 1 にマークを付けます
[例 1] 数値 x について、その 2 進数ビット (0 から始まり、下位から上位へ) の k 番目のビットを 1 に設定したいとします。
1 に設定する操作は、ビットごとの OR 操作を思い出させます。
その特性は、ビットまたは 1 の場合、結果は 1 であり、ビットまたは 0 の場合、結果は変化しません。
したがって、マーク コードの要件は次のとおりです。k 番目のビットは 1、他のビットは 0、つまり (1 << k) です。その場合、k 番目の位置を 1 に設定するステートメントは次のように記述できます。 (1 << k)。
2) 位置 0 をマークします
[例 2] 数値 x について、2 進数 (0 から始まり下位から上位へ) の k 番目のビットを 0 に設定したいとします。
ゼロセット演算はビット AND 演算を思い出させます。
その特徴は、ビットと上位 0 の場合、結果は 0 であり、ビットと上位 1 の場合、結果は変化しません。
したがって、マーク コードの要件は次のとおりです。k 番目のビットは 0、他のビットは 1、必要なのは (~(1 << k))、そして k 番目の位置が 0 であるというステートメントです。 x & ( ~(1 << k)) のように書くことができます。
3) フラグビットが反転される
[例 3] 数値 x について、2 進数の k 番目のビットを反転 (0 から始まり、下位から上位へ) したいとします。
否定演算は XOR 演算に関連付けられています。
その特性は、XOR 1、結果は否定され、XOR 0、結果は変更されません。
したがって、マーキング コードの要件は次のとおりです。k 番目のビットは 1、他のビットは 0、その値は (1 << k) です。次に、k 番目のビットを反転するステートメントは次のように記述できます: x ^ (1 << k) 。
3. マスクを生成する
同様に、左シフトを使用してマスクを生成し、数値の最後の k ビットに対して何らかの演算を実行できます。
(1 << k) のバイナリ表現は、1 に k 個の 0 を加えたもので、(1 << k) - 1 のバイナリ表現は k 個の 1 を表します。
最後の k ビットを 1 に変更します。これは、x | ((1 << k) - 1) のように記述できます。
最後の k を 0 に変更します。これは、x & ~((1 << k) - 1) のように記述できます。
最後の k ビットを反転します。これは、x ^ ((1 << k) - 1) と書くことができます。
11、右シフト演算子
1. 右シフトバイナリ形式
右シフト演算子は 2 項のビット演算子です。つまり、x >> y として表される 2 つのオペランドがあります。ここで、x と y は両方とも整数です。
x >> y は「x を y ビット右にシフトする」と発音され、ここでのビットはもちろん 2 進数のビットなので、これが意味するのは、まず x を 2 進数で表現し、正の数の場合は y だけ右にシフトするということです。 bits; 負の数の場合、y ビットを右シフトした後、上位ビットは 1 で埋められます。
例: 10 進数 87 の場合、その 2 進値は (1010111) で、y ビット左にシフトした結果は次のようになります。
2. 右シフトの実行結果
x >> y の実行結果は次と同等です。
この記号は四捨五入を表します。次のコード:
int main() {
int x = 0b1010111;
int y = 3;
printf("%d\n", x >> y);
return 0;
}
出力は次のとおりです。
この右シフト演算子の実際の意味は次のとおりです。
割り算は割り切れない可能性があるため、整数を削除するステップがあります。
3. 負の右シフトの実行結果
いわゆる負の数の右シフトは、x >> y で、x が負の数の場合、コードは次のようになります。
int main() {
printf("%d\n", -1 >> 1);
return 0;
}
その出力は次のとおりです。
次の式も満たすことがわかりました。
これは 2 の補数で説明できます。-1 の補数は次のようになります。
1 ビット右にシフトした後、負の数であるため、上位ビットに 1 が追加されて次が得られます。
- (x >> y) と (-x) >> y は同等であることがわかります。
それを整理するために簡単な質問をしてみましょう
[例 1] コードを実行する必要がなく、コードの出力がどの程度であるかを肉眼で確認できます。
int main() {
int x = (1 << 31) | (1 << 30) | 1;
int y = (1 << 31) | (1 << 30) | (1 << 29);
printf("%d\n", (x >> 1) / y);
return 0;
}
4. 負の数字を右にシフトするとどうなるか
先ほど x < 0 の場合について説明しましたが、y < 0 を試してみるとどうなるでしょうか? 次の性質も満たしますか?
それでも満たされる場合は、2 つの整数を左シフトすると小数が生成される可能性があります。
例を見てみましょう:
int main() {
printf("%d\n", 1 >> -1);
printf("%d\n", 1 >> -2);
printf("%d\n", 1 >> -3);
printf("%d\n", 1 >> -4);
printf("%d\n", 1 >> -5);
printf("%d\n", 1 >> -6);
printf("%d\n", 1 >> -7);
return 0;
}
正常に実行できますが、結果は期待どおりではないようで、次のような警告が報告されます。
[警告] 右シフト数が負です [-Wshift-count-negative]
実際、エディタは右にシフトするときに負の数を使用しないように指示しますが、その実行結果はエラーとみなされず、少なくとも例では正しいです。
負の数字を右にシフトすると、実際には、対応する正の数字を左にシフトするのと同じ効果が得られます。
12. 右シフト演算子の応用
1. 下位 k ビットを削除します
【例2】数値xが与えられたとき、その下位kビットを取り除いて出力します。
この問題は、x >> k のように右にシフトすることで直接解くことができます。
2. 下位ビットを連続して取ります 1
【例3】下位1が連続する数値xを求めて出力します。
数値 x については、下位ビットに k 個の連続する 1 があると仮定します。次のように:
次に、それに 1 を追加すると、次のようになります。
この時点で、これら 2 つの数値の XOR 演算の結果は次のようになります。
このとき、右に 1 ビットシフトすると、k 個の連続する 1 の値が得られます。これはまさに探しているものです。
したがって、次のステートメントを使用して検索できます: (x ^ (x + 1)) >> 1。
3. k 番目のビットの値を取得します
[例4] 数値xのk番目(0≦k≦30)ビットの値を求めて出力します。
2 進数の場合、k 番目のビットの値は 0 または 1 でなければなりません。
1 桁から k-1 桁までの数値については、私たちにとっては意味がありません。次のように、右シフトを使用して削除し、ビット AND 演算子を使用して 2 進数の最後のビットが 0 か 1 かを取得できます。( x >> k) & 1.
それでは次に、y < 0 を試してみるとどうなるでしょうか? 次の性質も満たしますか?
[外部リンク画像転送...(img-3wbr0PBV-1690102819771)]
それでも満たされる場合は、2 つの整数を左シフトすると小数が生成される可能性があります。
例を見てみましょう:
int main() {
printf("%d\n", 1 >> -1);
printf("%d\n", 1 >> -2);
printf("%d\n", 1 >> -3);
printf("%d\n", 1 >> -4);
printf("%d\n", 1 >> -5);
printf("%d\n", 1 >> -6);
printf("%d\n", 1 >> -7);
return 0;
}
正常に実行できますが、結果は期待どおりではないようで、次のような警告が報告されます。
[警告] 右シフト数が負です [-Wshift-count-negative]
実際、エディタは右にシフトするときに負の数を使用しないように指示しますが、その実行結果はエラーとみなされず、少なくとも例では正しいです。
負の数字を右にシフトすると、実際には、対応する正の数字を左にシフトするのと同じ効果が得られます。
12. 右シフト演算子の応用
1. 下位 k ビットを削除します
【例2】数値xが与えられたとき、その下位kビットを取り除いて出力します。
この問題は、x >> k のように右にシフトすることで直接解くことができます。
2. 下位ビットを連続して取ります 1
【例3】下位1が連続する数値xを求めて出力します。
数値 x については、下位ビットに k 個の連続する 1 があると仮定します。次のように:
【外部リンク画像転送…(img-d1hKcnX3-1690102819771)】
次に、それに 1 を追加すると、次のようになります。
【外部リンク画像転送...(img-5VGxiKFY-1690102819772)】
この時点で、これら 2 つの数値の XOR 演算の結果は次のようになります。
[外部リンク画像転送...(img-4z96Jhpo-1690102819772)]
このとき、右に 1 ビットシフトすると、k 個の連続する 1 の値が得られます。これはまさに探しているものです。
したがって、次のステートメントを使用して検索できます: (x ^ (x + 1)) >> 1。
3. k 番目のビットの値を取得します
[例4] 数値xのk番目(0≦k≦30)ビットの値を求めて出力します。
2 進数の場合、k 番目のビットの値は 0 または 1 でなければなりません。
1 桁から k-1 桁までの数値については、私たちにとっては意味がありません。次のように、右シフトを使用して削除し、ビット AND 演算子を使用して 2 進数の最後のビットが 0 か 1 かを取得できます。( x >> k) & 1.
リーコード関連の質問 13 件
問題解決のアイデア:
- カウンタ変数 count を 0 に初期化します。
- ループ変数 i を定義し、0 に初期化します。
- ループに入り、i が 32 未満かどうかを判断します (符号なし整数は 32 ビットであるため)。
- ループ内では、まずビット演算を使用して数値 n の i 番目のビットを 1 に設定し、その結果を n と比較します。結果が n と等しい場合、i 番目のビットが 1 であることを意味し、カウンタのカウントが 1 つ増加します。
- ループ変数 i が 1 ずつ増加し、次のループが続行されます。
- ループ終了後、最終カウント値 count を返します。
class Solution {
public:
int hammingWeight(uint32_t n) {
int count=0;
uint32_t i=0;
while(i<32)
{
if((n | (1<<i)) == n)count++;
i++;
}
return count;
}
};
問題解決のアイデア:
- カウンタ変数 count を 0 に初期化します。
- 左が右より小さいかどうかを判断するループに入ります。
- ループ内では、左右両方が右にシフトされ(2 で割ることに相当)、同時にカウンタのカウントがインクリメントされます。
- ループ終了後、count ビットだけ左シフトした結果を返します。
class Solution {
public:
int rangeBitwiseAnd(int left, int right) {
int count=0;
while(left<right)
{
left>>=1;
right>>=1;
++count;
}
return left<<count;
}
};
問題解決のアイデア:
- 各桁の出現回数をカウントするために使用される配列 count を長さ 32 で初期化します。
- 結果変数 result を 0 に初期化します。
- 最初のループに入ると、ループ変数 i は現在の統計の i 番目のビットを制御します。
- 最初のループ内で 2 番目のループに入り、ループ変数 num が配列 nums 内の各要素を走査します。
- 2 番目のループ内では、num の i 番目のビットが取り出され、i ビットの右シフトとビットごとの AND 演算、つまり現在のビット内の数値の出現数をカウントすることによって count[i] に追加されます。
- ループが終了した後、3 番目のループに入り、ループ変数 i が現在の統計の i 番目のビットを制御します。
- 3回目のループでは、count[i]からmod 3を取り、現在のビットに1回だけ出現する数値の値を取得し、それをiビット左にシフトした後、結果変数resultをビットごとに更新します。 OR演算。
- ループが終了すると、最終結果変数 result が返されます。
class Solution {
public:
int singleNumber(std::vector<int>& nums) {
int count[32] = {0}; // 用于统计每一位上的出现次数
int result = 0;
for (int i = 0; i < 32; i++) {
for (int num : nums) {
// 统计当前位上数字出现的次数
count[i] += (num >> i) & 1;
}
// 对统计结果取余,得到只出现一次的数字在当前位上的值
result |= (count[i] % 3) << i;
}
return result;
}
};
問題解決のアイデア:
- 結果変数 ans を 0 に初期化します。
- 配列 count を長さ 2^16 (つまり 65536) で初期化します。これは、各数値のビット単位の AND 結果の出現回数をカウントするために使用されます。
- 最初のループに入ると、ループ変数 x は配列 nums 内の各要素を走査します。
- 最初のループ内で 2 番目のループに入り、ループ変数 y が配列 nums 内の各要素を走査します。
- 2 番目のループ内で、ビットごとの AND 演算子を使用して、x と y のビットごとの AND 結果をインデックスとして使用し、count の対応する位置の値を 1 ずつ増やして、各ビットごとの AND 結果の出現回数をカウントします。
- 2 番目のループが終了した後、3 番目のループに入り、ループ変数 x が配列 nums 内の各要素を走査します。
- 3 番目のループ内で 4 番目のループに入り、ループ変数 i は 0 から 2^16-1 までのすべての整数を調べます。
- 4 番目のループ内で、(x & i) == 0 の条件が満たされているかどうかを判断し、満たされている場合は count[i] の値を結果変数 ans に加算します。
- 4 番目のサイクルが終了すると、最終結果変数 ans が返されます。
class Solution {
public:
int countTriplets(vector<int>& nums) {
int ans=0;
int count[1<<16]={0};
for(int x:nums)
for(int y:nums)
count[x&y]++;
for(int x:nums)
for(int i=0;i<1<<16;++i)
if((x&i)==0)ans+=count[i];
return ans;
}
};
問題解決のアイデア:
中間結果として変数 a を取得するには、配列 arr1 内のすべての要素を XOR する必要があります。これは、XOR 演算が結合法則と交換法則を満たすためです。つまり、要素の順序に関係なく結果は同じになります。
配列 arr2 内の各要素を反復処理し、それらに対してビットごとの AND 演算を実行し、結果を配列 arr2 に保存し直す必要があります。この目的は、arr2 および a の各要素に対してビット単位の AND 演算を実行し、各 (i, j) 数値ペアのビット単位の AND 結果を取得することです。
配列 arr2 を再度走査し、その中の各要素に対して XOR 演算を実行し、最終的な XOR と ans を取得します。
計算を完了するには、arr1 を走査するプロセスと arr2 を走査するプロセスである 2 つのループを実行するだけで済みます。そのため、時間計算量は O(N+M) になります。ここで、N は arr1 の長さ、M は arr2 の長さです。
この方法で記述する理由は、XOR 演算の性質を利用し、余分なスペースの使用を減らし、元の配列 arr2 を直接演算するためです。これにより、コード効率が向上し、メモリ領域が節約されます。
class Solution {
public:
int getXORSum(vector<int>& arr1, vector<int>& arr2) {
//(a&b)^(a&c)=a&(b^c)
int ans=0,a=0;
for(int i=0;i<arr1.size();++i){a^=arr1[i];}
for(int i=0;i<arr2.size();++i){arr2[i]&=a;}
for(int i=0;i<arr2.size();++i){ans^=arr2[i];}
return ans;
}
};
追記
質問を磨くために最も重要なのは基礎です!ベース!ベース!