アルゴリズムのブラッシングの質問 14 日目: ビット操作 - 2

 

目次

1、バイナリビットを反転する

1、少しずつ逆転

一連の考え

複雑さの分析

2.ビット演算分割統治

一連の考え

複雑さの分析

1、ビット演算

複雑さの分析

1、バイナリビットを反転する

190. 逆バイナリビット - LeetCode https://leetcode.cn/problems/reverse-bits/?plan=algorithms&plan_progress=gzwnnxs

1、少しずつ逆転

一連の考え

n を長さ 32 のバイナリ文字列として扱い、n の各ビットを下位から上位まで列挙し、それらを逆順に反転結果 rev に加算します。

コードの実装では、n は列挙されるビットごとに 1 ビット右にシフトされるため、現在の n の最下位ビットが列挙したいビットになります。n が 0 になるとループは終了します。

一部の言語 (\Java など) では符号なし整数型がないため、n の右シフト演算には論理右シフトを使用する必要があることに注意してください。

class Solution {
public:
    uint32_t reverseBits(uint32_t n) {
        uint32_t rev = 0;
        for (int i = 0; i < 32 && n > 0; ++i) {
            rev |= (n & 1) << (31 - i);
            n >>= 1;
        }
        return rev;
    }
};

複雑さの分析

  • 時間計算量: O(logn)。

  • 空間複雑度: O(1)。

2.ビット演算分割統治

一連の考え

バイナリ文字列を反転するには、文字列を左部分と右部分に分割し、各部分に対して反転操作を再帰的に実行し、左半分を右半分の後ろに配置して反転を完了します。

左側と右側の部分の計算方法は似ているため、ビットマスクとシフト演算を使用して、この分割統治プロセスをボトムアップで完了できます。

最低レベルの再帰では、すべてのパリティ ビットを交換する必要があります。

奇数ビットと偶数ビットをすべて取り出し、
奇数を偶数ビットにシフトし、偶数を奇数ビットにシフトします。
最後から 2 番目の層も同様に、2 ビットごとにグループ化し、グループ番号に従って奇数グループと偶数グループをすべて取り出し、奇数グループを偶数グループに移動し、偶数グループを奇数グループに移動します。等々。

一部の言語 (Java など) では符号なし整数型がないため、nn の右シフト演算には論理右シフトを使用する必要があることに注意してください。

class Solution {
private:
    const uint32_t M1 = 0x55555555; // 01010101010101010101010101010101
    const uint32_t M2 = 0x33333333; // 00110011001100110011001100110011
    const uint32_t M4 = 0x0f0f0f0f; // 00001111000011110000111100001111
    const uint32_t M8 = 0x00ff00ff; // 00000000111111110000000011111111

public:
    uint32_t reverseBits(uint32_t n) {
        n = n >> 1 & M1 | (n & M1) << 1;
        n = n >> 2 & M2 | (n & M2) << 2;
        n = n >> 4 & M4 | (n & M4) << 4;
        n = n >> 8 & M8 | (n & M8) << 8;
        return n >> 16 | n << 16;
    }
};

複雑さの分析

  • 時間計算量: O(1)。

  • 空間複雑度: O(1)。

1、ビット演算


時間計算量と空間計算量の制約を考慮しない場合、この問題には多くの解決策があり、考えられる解決策は次のとおりです。

数値を保存するにはセットを使用します。配列内の各数値を調べます。数値がセットに存在しない場合は、その数値をセットに追加します。数値が既にセットに存在する場合は、その数値をセットから削除します。最後に残った数値が表示される数値になります。一度だけ。

ハッシュ テーブルを使用して、各数値とその数値が出現する回数を保存します。配列を走査することで、各数値の出現回数を取得してハッシュ テーブルを更新し、最後にハッシュ テーブルを走査して 1 回だけ出現する数値を取得します。

セットを使用して配列内に出現するすべての数値を格納し、配列内の要素の合計を計算します。セットでは重複する要素がないことが保証されているため、セット内のすべての要素の合計が 2 回計算されます。これは、各要素が 2 回出現する場合の要素の合計です。配列内の 1 つの要素のみが 1 回出現し、残りの要素は 2 回出現するため、配列内の要素の合計がセット内の要素の合計の 2 倍から減算され、残りの数値がのみ出現する数値になります。配列に一度。

上記の 3 つの解決策はすべて、O(n) の追加スペース使用量を必要とします (n は配列の長さ)。

線形時間計算量と一定空間計算量を実現するにはどうすればよいでしょうか?

答えは、ビット単位の演算を使用することです。この問題には、XOR 演算 ⊕ を使用できます。XOR演算には以下の3つの性質があります。

任意の数値と 0 の間で XOR 演算を実行すると、結果は元の数値のままになります (a⊕0=a)。
任意の数値はそれ自体と XOR 演算され、結果は 0、つまり a⊕a=0 になります。
XOR 演算は交換法則と結合法則、つまり a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b を満たします。

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

複雑さの分析

  • 時間計算量: O(n)、n は配列の長さです。配列を 1 回繰り返すだけで済みます。

  • 空間複雑度: O(1)。

おすすめ

転載: blog.csdn.net/m0_63309778/article/details/126756751