-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Number of 1 Bits #46
base: main
Are you sure you want to change the base?
Number of 1 Bits #46
Conversation
|
||
rightmost set bitは n & (n - 1) で求めることができるので、効率的にcountしていくことができる。 | ||
*/ | ||
func hammingWeightStep1(n int) int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
複数回この関数が呼ばれるならどう最適化しますか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
たとえばあらかじめnビット列の全てのパターンについてハミングウェイトを計算しておき、それを使って入力されたビット列のハミングウェイトを計算するというのはいかがでしょうか。
30ビットぐらいであれば、4GBぐらいに収まると思うので現実的な気がします。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
コードも書いてみた方が練習として良さそうです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ありがとうございます、承知しました🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
全てのパターンに対して事前計算した結果を利用するのとその都度計算するのはどちらが速そうですか?
この問題なら32ビット列ですが、64ビットまたはそれ以上ならどうしましょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この関数の場合、一度のループで3nsぐらいかかるかなと思ってまして、それをsetされているビット数分繰り返しただけの時間がかかると思います。パターンを保持しておく方法だと、10ビットぐらいであれば1次キャッシュに載りますし、20ビット弱ぐらいなら2次キャッシュに載るかと思いますので、1~10nsぐらいで求まるかと思います。ただし30ビットぐらいになるとメモリにアクセスする必要が出てくると思うので、メモリアクセスに100nsぐらいかかるとすると、その都度計算する方法の方が速くなってくるかもしれません。
保持している以上のビット数が来た場合ですが、その場合は都度nビット分だけ取り出すということを繰り返して、合計を求めるということができるかと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なので、10bitぐらいのパターンを保持しておいて、都度10ビット分だけ取り出すということを繰り返して、合計を求めるというのが一番良いのではないかと思いました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
その方針で良さそうですね。
/* | ||
時間:3分 | ||
|
||
rightmost set bitは n & (n - 1) で求めることができるので、効率的にcountしていくことができる。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
まだ見てないです。頭から抜けてました。これも参考にして書き直してみます。
この辺りも読むと良いかもしれません。 分割統治法を用いた効率的なアルゴリズムは、ソフトウェアエンジニアの常識には含まれてはいないと思うのですが、「ハッカーのたのしみ: 本物のプログラマはいかにして問題を解くか」に載っているため、知ってる人は知っていると思います。 |
Number of 1 Bitsを解きました。レビューをお願い致します。
問題:https://leetcode.com/problems/number-of-1-bits/
言語:Go
すでに解いている方々:
Kitaken0107/GrindEasy#24
関連→ #44
ハミング重み、ハミング距離
https://ja.wikipedia.org/wiki/ハミング重み
バイナリ表現においては、ハミング重みは、population count, popcount, sideways sum, bit summitationなどと呼ばれたりもする。x86などにpopcntという命令があるなど、機械語命令としても存在することがある(Hamming weight > Efficient implementation - Wikipedia、参照)。
https://ja.wikipedia.org/wiki/ハミング距離
AとBのハミング距離は、
A XOR B
のハミング重みと等しい。2の補数
位取り基数法
位取り基数法とはいくつかの数字を並べて数を表す方法である(1, 2, 3を並べて123を表すなど)。1つの桁を$N$ 種の数字列の組み合わせで表す位取り基数法を $N$ 進位取り基数法または $N$ 進法と呼ぶ。
2の補数とは
2の補数とは、2を位取り基数法の基数とした場合の基数の補数(つまり2進数の補数)である。補数とは、ある数$x$ との和が基準となる数 $C$ に等しくなるような数である。補数を $xc$ とするとき、 $C = x + xc$ となる。よって $x = C - xc$ である(同時に $xc = C - x$ にもなることから、 $x$ は $xc$ の補数でもある)。つまり、 $x$ の2の補数は $2^n - x$ である。たとえば$2^4 - 11 = 5$ なので
1011
(11)の2の補数は101
になる。ビット操作で2の補数を得る
または$x$ のビットを反転させて1を足すことで $x$ の2の補数を得ることもできる。たとえば
1011
の2の補数を求めたい場合はまずビットを反転させて100
を得て、それに1を足すことで101
が2の補数だとわかる。1011
のビットを反転させるということはつまり1111
は2の補数を使った減算
たとえば$11 - 5$ という減算を行う場合、これは5ビットの符号あり整数を使うと $01011 + 11011 = 100110$ となり(5は $00101$ なので-5は $11011$ になる)、
100110
はオーバーフローしていることから最終的な減算結果は00110
(6)となる。引き算用の回路を作らなくて済むので、コンピュータの世界では負の整数は2の補数を使って表すことが一般的である。なのでたとえば4の2の補数が-4を表すことになる。4の2の補数は5ビットの整数なら、この場合は28になる($2^5 - 4$ )ので、
11100
が-4を表すことになる。反対に-28を表したい場合は28の補数は4になるので、00100
となる。ここで、5ビットを使って、負の整数だけでなく正の整数も扱いたいとなった場合を考える。
11100
や00100
というビットの並びがあったときに、これが正の整数を表しているのか負の整数を表しているのかがこの並びを見るだけではわからない。11100
は-4を表しているかもしれないし、28を表しているかもしれない。同様に00100
も-28を表しているかもしれないし、4を表しているかもしれない。これだと不便なので、MSB(一番位の高いビット)が0の場合に正の整数、1の場合に負の整数を表しているものと決めてしまう。そうすると、
00000
(0) ~01111
(15)が正の整数を、11111
(-1) ~10000
(-16)が負の整数を表すようになる。MSBが1の場合に正の整数、0の場合に負の整数を表すものとしてしまうことも可能だが、その場合は10000
(16) ~11111
(31)が正の整数を、01111
(-17) ~00000
(-32)が負の整数を表すようになり、表せる範囲が-32~-17, 16~31となってしまうので、使い勝手が良くない。(補足)1の補数について
ちなみに$b$ 進数を基数とする場合の $x$ に対する減基数の補数は $(b^n - 1) - x$ と定義される。基数が文脈上明らかなら単に $(b-1)$ の補数と呼ばれる。そのため、2進法における減基数の補数は、1の補数と呼ばれる。
たとえば$(2^4 - 1) - 11 = 4$ なので
1011
(11)の2の補数は0100
になる。つまりビットを単に反転させることで1の補数を得ることができる(2の補数を求めたい場合はビットを反転させた後に1を足す必要がある)。そのため1の補数は同じゼロを表現する際に、正のゼロ(0000
)と負のゼロ(1111
)の2つのゼロが存在することになる。Rightmost set bit
Rightmost set bitをunsetする方法
Rightmost set bitをunsetするには、単純に実装すると右シフトを繰り返していくことで実現できるが、この場合はRightmost set bitが$n$ 桁目に存在する場合に、 $O(n)$ の時間計算量になる。
効率的にRightmost set bitをunsetするには、$O(1)$ になる。
n & (n-1)
とするか2の補数を使うかの2つの方法がある。この場合、Rightmost set bitをunsetする時間計算量はn & (n-1)
たとえば$n$ が$n-1$ は$n-1$ とすると繰り下がるため、Rightmost set bitは0になり、Rightmost set bitの下の位のビットは全て1になる。そのため
10100
のとき、10011
となる。10001
のときは10000
になる。つまり、n & (n-1)
とすることで、100...
と011...
のANDがとられるので000...
になり、Rightmost set bitがunsetされる。Turn off the rightmost set bit
2の補数を使う
前述したように$n$ ビットの世界では、 $xc = -x$ なので、単に $-x$ とすることで、 $x$ の2の補数を得ることができる。またこれも前述したように、2の補数は $x$ のビットを反転させて1を足したものである。たとえば$x$ とその2の補数はお互いにRightmost set bit以外が反転しているということである。つまり、$x$ のRightmost set bitをunsetすることができるのである。
10110
の2の補数は01010
であり、10001
の2の補数は01111
になる。ここで注目したいのは、x & -x
とすると、Rightmost set bitのみが1になったビット列を得ることができる。よって、x - (x & -x)
としてあげれば、Unset he rightmost set bit using 2s complement
Rightmost unset bitをsetする方法
Rightmost unset bitとは最下位の0になっているbitのことである。たとえば$x$ のビットを反転させた
1011
なら3番目のビットが、1100
ならLSBがRightmost unset bitである。このRightmost unset bitをsetするには、^x
を得て、そのRightmost set bitをunsetし、最後にそれを再度反転させれば、Rightmost unset bitをsetすることができる。Rightmost set bitをunsetする方法は前述のとおりである。Set the rightmost unset bit