Pythonでビットを数える:0〜numまでの各整数の2進数における「1」の個数をO(n)で求める方法
問題概要
非負整数 num が与えられたとき、0 ≤ i ≤ num を満たす各整数 i について、その2進表現に含まれる「1」の個数を計算し、結果をリストとして返すことを考えます。
例えば num = 5 の場合、対象となる数は [0, 1, 2, 3, 4, 5] です。それぞれの数を2進数で表すと次のようになり、1の個数は [0, 1, 1, 2, 1, 2] となります。
| 数値 | 2進表現 | 1の個数 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 1 | 1 |
| 2 | 10 | 1 |
| 3 | 11 | 2 |
| 4 | 100 | 1 |
| 5 | 101 | 2 |
解法のアプローチ(動的計画法)
この問題は、すでに計算した結果を再利用する動的計画法(DP)の考え方を使うことで、各数値を1度ずつ処理するだけで全体を O(n) の時間計算量で解けます。
ポイントは次の2つです。
- 2のべき乗の判定:
i & (i - 1) == 0が成り立つとき、iは2のべき乗です。2のべき乗の2進表現には「1」が1つしか含まれないため、result[i] = 1とできます。 - オフセットの利用: 直前の2のべき乗を基準とした相対位置
offsetを管理します。iが2のべき乗でない場合、iの2進表現は「offsetの2進表現の先頭に1を付け加えたもの」と一致するため、result[i] = 1 + result[offset]という漸化式が成り立ちます。
アルゴリズムの手順
res: 要素数num + 1の配列を用意し、すべて0で初期化するoffset: 0で初期化するiを 1 からnumまで順に処理するi & (i - 1) == 0の場合(iが2のべき乗):res[i] = 1とし、offset = 0にリセットする- それ以外の場合:
offsetを1増やし、res[i] = 1 + res[offset]とする
resを返す
Pythonでの実装例
以下の実装を見ると、仕組みがより理解しやすくなります。
class Solution:
def countBits(self, num):
result = [0] * (num + 1)
offset = 0
for i in range(1, num + 1):
if i & i - 1 == 0:
result[i] = 1
offset = 0
else:
offset += 1
result[i] = 1 + result[offset]
return result
ob1 = Solution()
print(ob1.countBits(6))
入力
6
出力
[0, 1, 1, 2, 1, 2, 2]
なお、Pythonではビット演算子 & は比較演算子よりも優先順位が高いため、i & i - 1 == 0 は (i & (i - 1)) == 0 と解釈され、括弧がなくても意図どおりに動作します。
別解との比較
より簡潔に書きたい場合は、組み込み関数を使った次の方法もあります。
def count_bits(num):
return [bin(i).count('1') for i in range(num + 1)]
この方法は直感的で読みやすい一方、各数値ごとに2進文字列を生成するため、入力が大きくなるとDPを使った手法の方が効率的です。また、result[i] = result[i >> 1] + (i & 1) という漸化式を使えば、オフセット変数なしで同様のDPを実装することも可能です。
計算量
- 時間計算量: O(n) — 各数値を1度ずつ処理するだけです
- 空間計算量: O(n) — 結果を格納する配列が必要です
-
Pythonでカウントソートを実装する方法|サンプルコード付きで解説
この記事では、以下の問題文に対する解決策について詳しく解説します。 問題文 問題: 配列が与えられたとき、カウントソート(Counting Sort)のアルゴリズムを用いて、その配列を昇順に並べ替えます。 カウントソートとは? カウントソートは、あらかじめ決められた範囲内のキーを対象として動作する整列アルゴリズムです。まず、それぞれ異なるキー(値)を持つ要素がいくつあるかを数え上げます。その後、累積和の計算を行うことで、各要素がソート後の配列のどの位置に配置されるべきかを求め、結果を出力します。 この手法は、キーの取り得る範囲が狭い場合に特に有効で、時間計算量は O(n + k)(n は要素数
-
【初心者向け】Pythonのissuperset()メソッドの使い方をわかりやすく解説
はじめにこの記事では、Pythonのissuperset()メソッドについて、基本的な仕組みから実際のコード例まで詳しく解説します。issuperset()は、セット(集合)に対して使用できるメソッドで、引数として渡されたセットのすべての要素が、呼び出し元のセットに含まれているかどうかを判定します。呼び出し元のセットBが、引数のセットAのすべての要素を含んでいる場合 → True を返すセットAの要素がすべてBに含まれていない場合 → False を返すつまり、「BがAの上位集合(スーパーセット)であるかどうか」を判定するためのメソッドです。基本構文B.issuperset(A)この式は、Bが