Skip to content
Merged
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
184 changes: 184 additions & 0 deletions Recursion/50/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# 進め方
- step1: 5分考えて分からなかったら答えを見る。答えを理解したら、答えを隠して書く。筆が進まず5分立ったら答えを見る。答えを送信して正解するまで。
- step2: コードを読みやすく整える。動くコードになったら終了。
- step3: 時間を計りながら書く。10分以内に3回連続でアクセプトされるまで。

# step1: 15分
nがすごく大きい。

下記の実装だと再帰が深くなりすぎて落ちる。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Python の再帰回数の上限については、以下をご参照ください。
https://docs.python.org/3/library/sys.html#sys.setrecursionlimit
https://docs.python.org/3/library/sys.html#sys.getrecursionlimit

参考までに自分の環境ではデフォルト値は 1000 でした。

LeetCode は、デフォルトで大きめの値に設定されていたと思います。

```python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n < 0:
return 1 / x * self.myPow(x, n + 1)
else:
return x * self.myPow(x, n - 1)
```

呼び出しごとに深さが半分になるようにした。O(nlog(n))になる、これでもTLEするだろう。
再帰呼び出しの木を書いたら同じ計算をたくさん行ってることに気づいたのでキャッシュした。
こうすると時間計算量はO(log(n))になる。
ざっくり評価なので実行時間ちゃんと見積もれるようになりたい。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

実行時間の見積もり方については、以下のコメントが参考になるかもしれません。
Yuto729/LeetCode_arai60#16 (comment)

```python
from functools import cache


class Solution:
@cache
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n == 1:
return x
if n == -1:
return 1 / x

half = n // 2
return self.myPow(x, n - half) * self.myPow(x, half)

```



# step2: 30分
self.myPow(x * x, n // 2)にすればキャッシュする必要がないので空間計算量も減らせる

```python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1

if n >= 0:
base = x
exponent = n
else:
base = 1 / x
Comment on lines +58 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

本問の入力制約ではありえないですが、xが0のとき、ここでZeroDivisionErrorとなってしまうので、想定した条件分岐を追加してみても良いかもしれません。

0^0 == 0 にすることが多いですが、どちらにしてもどちらから近づくかで極限が異なるので難しい問題をはらんでいます。

https://discord.com/channels/1084280443945353267/1262688866326941718/1351742235238076458

https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.d9ky6ipkmw98

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

あ、すみません。0^0 == 1 にすることのほうが多いと思います。(これは数学の話なので、あんまりプログラミングは関係ないですが。)

exponent = -n
if exponent % 2 == 1:
return base * self.myPow(base ** 2, (exponent - 1) // 2)
return self.myPow(base ** 2, exponent // 2)
```

同じことをループで実現
```python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1

if n >= 0:
base = x
exponent = n
else:
base = 1 / x
exponent = -n
result = 1
while exponent != 0:
if exponent % 2 == 1:
result *= base
exponent -= 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.

下で切り捨て除算しているのでこの行は不要に見えます。

base = base ** 2
exponent //= 2
Comment on lines +80 to +85
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

divmodを使ってみることもできます。好みの範囲ですね。

        while exponent:
            exponent, least_significant_bit = divmod(exponent, 2)
            if least_significant_bit:
                result *= base
            base = base ** 2

https://docs.python.org/3/library/functions.html#divmod


return result
```

https://github.com/Satorien/LeetCode/pull/45/files
再帰の実装で、nが奇数のときに、`return x * self.myPow(x, n - 1)`とする実装も分かりやすいとは思った。
ただ奇数のときの呼び出し回数が増えるので最大で倍の呼び出し回数になってしまうか。呼び出し回数による速度劣化と分かりやすさのトレードオフになりそう。

nを2進数表記したときに1が立っている箇所を掛け算する実装もある。今までの解法の方が分かりやすいが。
```python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n < 0:
return self.myPow(1 / x, -n)

base = x
exponent = n
bit = 1
result = 1
while exponent >= bit:
if exponent & bit > 0:
result *= base
bit <<= 1
base *= base
Comment on lines +103 to +111
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に対し左シフトしていき、逐次1の位をみるという風にも書けますね。

        base = x
        exponent = n
        result = 1
        while exponent:
            if exponent & 1:
                result *= base
            exponent >>= 1
            base *= base

変数bitを消せるので自分はこちらのが好みですね。


return result
```


# step3: 15分
※間違えがあればn回目を増やす

## 1回目
```python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n < 0:
return self.myPow(1 / x, -n)

base = x
exponent = n
bit = 1
result = 1
while bit <= exponent:
if exponent & bit > 0:
result *= base
base *= base
bit <<= 1

return result
```

## 2回目
```python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n < 0:
return self.myPow(1 / x, -n)

base = x
exponent = n
bit = 1
result = 1
while bit <= exponent:
if exponent & bit != 0:
result *= base
base *= base
bit <<= 1

return result
```

## 3回目
```python
class Solution:
def myPow(self, x: float, n: int) -> float:
if n == 0:
return 1
if n < 0:
return self.myPow(1 / x, -n)

base = x
exponent = n
bit = 1
result = 1
while bit <= exponent:
if exponent & bit != 0:
result *= base
base *= base
bit <<= 1

return result
```