-
Notifications
You must be signed in to change notification settings - Fork 0
50. Pow(x, n) #45
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
50. Pow(x, n) #45
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| # 進め方 | ||
| - step1: 5分考えて分からなかったら答えを見る。答えを理解したら、答えを隠して書く。筆が進まず5分立ったら答えを見る。答えを送信して正解するまで。 | ||
| - step2: コードを読みやすく整える。動くコードになったら終了。 | ||
| - step3: 時間を計りながら書く。10分以内に3回連続でアクセプトされるまで。 | ||
|
|
||
| # step1: 15分 | ||
| nがすごく大きい。 | ||
|
|
||
| 下記の実装だと再帰が深くなりすぎて落ちる。 | ||
| ```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))になる。 | ||
| ざっくり評価なので実行時間ちゃんと見積もれるようになりたい。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 実行時間の見積もり方については、以下のコメントが参考になるかもしれません。 |
||
| ```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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 本問の入力制約ではありえないですが、xが0のとき、ここでZeroDivisionErrorとなってしまうので、想定した条件分岐を追加してみても良いかもしれません。
https://discord.com/channels/1084280443945353267/1262688866326941718/1351742235238076458 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 下で切り捨て除算しているのでこの行は不要に見えます。 |
||
| base = base ** 2 | ||
| exponent //= 2 | ||
|
Comment on lines
+80
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| ``` | ||
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.
Python の再帰回数の上限については、以下をご参照ください。
https://docs.python.org/3/library/sys.html#sys.setrecursionlimit
https://docs.python.org/3/library/sys.html#sys.getrecursionlimit
参考までに自分の環境ではデフォルト値は 1000 でした。
LeetCode は、デフォルトで大きめの値に設定されていたと思います。