C++でのバイナリ表現の循環順列:グレイコードによる効率的な実装
問題の概要
2つの整数 n と start が与えられたとき、0 から 2^n − 1 までの整数の順列 p を、以下の条件を満たすように求める問題を考えます。
- p[0] = start であること
- 隣接する要素 p[i] と p[i+1] の2進数(バイナリ)表現は、1ビットのみ異なること
- 最初と最後の要素である p[0] と p[2^n − 1] も、1ビットのみ異なること
たとえば、n = 2、start = 3 が入力された場合、答えは [3, 2, 0, 1] となります。これを2進数で表すと [11, 10, 00, 01] であり、隣り合う値同士(最後の要素と最初の要素を含む)が必ず1ビットだけ異なっていることが確認できます。
解法のアプローチ:グレイコードの活用
この問題を効率的に解く鍵となるのがグレイコード(Gray code)です。グレイコードとは、隣接する数値の2進表現が必ず1ビットしか異ならないように並べた数列のことです。
n ビットのグレイコードは、次のシンプルな式で生成できます。
g(i) = i XOR (i >> 1)
さらに、このグレイコード全体に start をXOR演算で適用することで、任意の値から始まる循環順列が得られます。XORの性質により、「隣接要素が1ビット違い」という性質は保たれたまま、最初の要素が必ず start になります。
アルゴリズムの手順
- 結果を格納する配列 ans を用意する
- i を 0 から 2^n − 1 まで繰り返し、ans に「start XOR i XOR (i / 2)」を挿入する
- ans を返す
C++での実装例
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
cout << "[";
for(int i = 0; i<v.size(); i++){
cout << v[i] << ", ";
}
cout << "]"<<endl;
}
class Solution {
public:
vector<int> circularPermutation(int n, int start) {
vector <int> ans;
for(int i = 0 ; i < 1<<n; i++){
ans.push_back(start ^ i ^(i>>1));
}
return ans;
}
};
main(){
Solution ob;
print_vector(ob.circularPermutation(5,3));
}入力
5 3
出力
[3, 2, 0, 1, 5, 4, 6, 7, 15, 14, 12, 13, 9, 8, 10, 11, 27, 26, 24, 25, 29, 28, 30, 31, 23, 22, 20, 21, 17, 16, 18, 19]
計算量の評価
このアルゴリズムは 0 から 2^n − 1 までの各値を一度ずつ処理するため、時間計算量は O(2^n)、結果を格納するための空間計算量も O(2^n) となります。ビット演算のみで構成されているためオーバーヘッドが極めて小さく、n = 20 程度の規模でも実用的な速度で動作する点が大きなメリットです。
-
C++で数値を2進数表現に変換する方法【再帰処理を解説】
2進数(バイナリ数)とは、0と1という2つの数字のみで構成される数値表現のことです。例えば、01010111 のような形で表されます。コンピュータの内部では、すべてのデータがこの2進数として扱われています。 ある数値を2進数形式で表現する方法はいくつかあります。本記事では、代表的な「再帰を使った方法」を中心に解説します。 再帰を用いた方法 この方法では、再帰呼び出しを利用して数値を2進数形式で表現します。数値を2で割り続けながら、その余りを順に出力していくことで、2進数表現を得ることができます。 アルゴリズム ステップ1: 数値が1より大きい場合、ステップ2とステップ3を実行します。 ステップ
-
Pythonで数値の2進表現が回文かどうかを判定する方法
ある整数 n が与えられたとき、その2進表現が回文(前から読んでも後ろから読んでも同じ並び)になっているかどうかを判定する方法を解説します。 例えば、入力が n = 9 の場合を考えてみましょう。9の2進表現は「1001」であり、これは回文であるため、出力は True になります。 解法のアプローチ この問題は、数値の2進表現をビット単位で反転し、元の数値と比較することで解けます。具体的な手順は以下の通りです。 ans を 0 で初期化する num が 0 より大きい間、次の処理を繰り返す ans を1ビット左シフトする(ans × 2 と同等) num が奇数(最下位ビットが1)なら、