Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions 50_pow_x_n/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 50. Pow(x, n)

https://leetcode.com/problems/powx-n/

## Comments

### step1

* 特に初見ではなく、ああ、素直に n 乗すると TLE するやつね、という感じ。
* -2^31 <= n <= 2^31-1
* 2^31 = 2,147,483,648 ≈ 2B くらい = 2 * 10^9
* 2^32 = 4,294,967,296 = 4B = 4 * 10^9くらい
* 2^64 = 18,446,744,073,709,551,616 ≈ 18 * 10^18
* trillion -> quadrillion 以上は面倒で調べるのやめた
* 実際の数字見ると、C++ だったらぎりぎり計算できるのかな?
* 最初 `Solution1WA` を書いた。
* しばらく悩んでそもそもアルゴリズムがよくわかってなかったので解答を見て `Solution2AC` を書いた。
* n < 0 がありうるのを見逃していした
* n % 2 の判定を 1 回しかしていなかったり、result *= result (base *= base ではなく) していたり、そもそもアルゴリズムをちゃんと理解していなかった
* LeetCode の書き方は x, n を破壊しているんだけど、変数名も気に食わないし、base と exponent にした。
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

数値はイミュータブルなので、破壊という表現に違和感があります。

* n == 0 は分けて処理しなくて良い。ループに入らずにresult の初期値のまま返る。
* 解法としては再帰もあるけどループが好き

### step2

* LeetCode にあるようなこういうの手書きしてみたらよかったかな

```
2^10 = (2 * 2)^5
4^5 = 4 * 4^4
4^5 = 4 * (4 * 4)^2
4 * 16^2 = 4 * (16 * 16)^1
4 * 256^1 = 4 * 256 * (256)^0
```

* https://github.com/fuga-98/arai60/pull/45/files

* 小数について IEEE 754 などまだ見れていないので後で見る。backlog に追加
* https://docs.python.org/ja/3.13/tutorial/floatingpoint.html
* https://github.com/ryosuketc/leetcode_arai60/pull/22/files#r2121653075
* ちょっと wiki が重かったので応用情報の教科書で勉強した。
* 浮動小数を`±m * 2^e` で表す
* 日本語だと、符号、仮数 (m)、基数、指数 (e)
* 英語だと、sign, significand, base, exponent かな。英語の方がわかりやすい
* m は通常何らかの形で正規化する。例えば 0.m で正規化する。
* たとえば符号に 1 ビット、指数に 7 ビット、仮数に 24 ビット使うなど
* IEEE 754 だと
* 定義 (32 bits)
* sign: 1 bit
* exponent: 8 bits (base 2 として、+127 -> bias 127)
* significand: 23 bits (significand - 1 の 2 進小数、1.xxx の小数部)
* 64 bits だと、exponent = 11, significand = 52
* significand を 1.xxx で表す。最初の 1. は暗黙に扱えるので 0.m よりも 1 ビット多く扱える。
* 指数は正負取りうる。負の数を 2 の補数で表すこともできるが、IEEE 754 では +127 して 0 に合わせている。
* 応用情報の教科書だと、IEEE 754 の仮数 significand - 1 している?
* というかそもそも本当に IEEE754 だと -1 しているんだっけ
* https://ja.wikipedia.org/wiki/IEEE_754 とちょっと記述違うかも。
* 大まかな理解はした。応用情報の教科書と wiki の差分はまだそんなに追えてない。
* ビットを使う解法
* https://github.com/olsen-blue/Arai60/pull/45/files
* https://github.com/hayashi-ay/leetcode/pull/41/files#r1515417145
* e.g. 3^14 = 3^(2^3) * 3^(2^2) * 3^(2^1)
* 正直なところまだ腹落ちしきっていない (powered / cumulated が自然言語で何を示すのか言語化できていない) が、時間的都合で一旦 backlog 行き…。あとで悖る。ビット演算の復習はすべきかも。
* ビットの方がコンピュータっぽいが、数学的な (人間がやる) 計算を模倣した step1 のやり方ができるのが先かなということで step1 の方で練習。

### step3

* 2:30
* 書いていて思ったが step2 のビットで腹落ちしていないのって、変数名の定義の他には、多分こちらの (step1 ベースの) 解答でも当てはまっていて、result を、exponent % 2 == 1 のときにしかアップデートしていないからな気がする?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これは何も分かっていない感じがします。myMultiply にして *= を += に置き換えたら分かりますか?

* result はループの中で (exponent が減るときは) 毎回アップデートされてほしいという気持ちがあるかも。なんか base の方にためておいて、exponent % 2 == 1 のときに一気にアップデートするというのに納得がいってない気がする。。
30 changes: 30 additions & 0 deletions 50_pow_x_n/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class Solution1WA:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

細かいですが、「floatを返す」としているので、1.0でも良いですね

result = x
exponent = n
while exponent != 1:
result *= result
exponent //= 2
if n % 2 == 1:
result *= x
return result


class Solution2AC:
def myPow(self, x: float, n: int) -> float:
if n < 0:
n = -n
x = 1.0 / x
result = 1
exponent = n
base = x
while exponent != 0:
if exponent % 2 == 1:
result *= base
exponent -= 1
base *= base
exponent //= 2

return result
24 changes: 24 additions & 0 deletions 50_pow_x_n/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution:
def myPow(self, x: float, n: int) -> float:
if n < 0:
x = 1.0 / x
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n<0で分母に0がくる可能性があるので、x=0の時を早期リターンしても良いかと思いました。

n = -n

powered = 1
cumulated = x
exponent = n
# e.g. 3^14 = 3^(2^3) * 3^(2^2) * 3^(2^1)
# bin(14) == 0b1110

# e.g. 3^5 = * 3^(2^2) * 3^(2^0)
# bin(14) == 0b101
# powered=1, cumulated=3.0, exponent=5
# powered=3.0, cumulated=9.0, exponent=2
# powered=3.0, cumulated=81.0, exponent=1
while exponent > 0:
print(f'powered={powered}, cumulated={cumulated}, exponent={exponent}')
if exponent & 1:
powered *= cumulated
exponent >>= 1
cumulated *= cumulated
return powered
15 changes: 15 additions & 0 deletions 50_pow_x_n/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Solution:
def myPow(self, x: float, n: int) -> float:
if n < 0:
n = -n
x = 1.0 / x
base = x
exponent = n
result = 1
while exponent != 0:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exponent > 0のほうが好みです。
なんか、ちゃんと終了してくれそうなので。

if exponent % 2 == 1:
result *= base
exponent -= 1
base *= base
exponent //= 2
return result