-
Notifications
You must be signed in to change notification settings - Fork 0
50 pow x n medium #16
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
base: main
Are you sure you want to change the base?
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,146 @@ | ||
| # 問題へのリンク | ||
| [50. Pow(x, n)](https://leetcode.com/problems/powx-n/) | ||
|
|
||
| # 言語 | ||
| Python | ||
|
|
||
| # 問題の概要 | ||
| 浮動小数点数 `x` と整数 `n` が与えられたとき、`x` の `n` 乗を計算する。 | ||
|
|
||
| # 自分の解法 | ||
|
|
||
| ## step1 | ||
| まず、繰り返し二乗法を再帰関数を用いて実装した。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| """ | ||
| Compute x to the power of n based on these four facts; | ||
| - x^(-n) = (1/x)^n (n>0) | ||
| - x^0 = 1 | ||
| - x^1 = x | ||
| - if n = 2*q + r, then x^(n) = (x^q)^2 * (x)^r | ||
|
|
||
| """ | ||
| if n < 0: | ||
| return self.myPow(1 / x, -n) | ||
| if n == 0: | ||
| return 1.0 | ||
| if n == 1: | ||
| return x | ||
| quotient, remainder = n // 2, n % 2 | ||
|
|
||
| return self.myPow(x, quotient) ** 2 * self.myPow(x, remainder) | ||
| ``` | ||
|
|
||
| - `n`が偶数の場合は `x^n = (x^(n/2))^2`、奇数の場合は `x^n = x * (x^((n-1)/2))^2` となる性質を利用した再帰的な解法である。 | ||
| - `n`を2で割っていくため、再帰の深さは`log(n)`となる。 | ||
| - 時間計算量:`O(log(n))`。各`n`に対する計算は一度しか行われないためである。 | ||
| - `remainder`は常に0または1であり、この計算はたかだか定数時間であるため、全体の計算量に大きな影響はない。(メモ化は不要) | ||
| - (個人的には)`n%2`が1か0かで場合分けするよりも、`remainder`をそのまま`myPow`に渡す方がわかりやすいと感じた。数学的に必ず`n=2*q+r`なら`x^n=(x^q)^2 * (x)^r`となるため | ||
| - 空間計算量:`O(log(n))`。再帰呼び出しのスタックに`log(n)`の空間が必要である。 | ||
|
|
||
| ### 発見 | ||
| - べき乗演算子`**`の挙動について、`func(x) ** 2` のような式は `func(x)` を1回だけ評価することを確認した。 | ||
|
|
||
| ## step2 | ||
| step1の再帰実装では空間計算量が`O(log(n))`であったため、これを`O(1)`に改善すべく、反復処理による実装に変更した。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| """ | ||
| Compute x to the power of n using doubling. | ||
| Example: x=2.0, n = 10 | ||
| n = 2^3 + 2^1 | ||
| x^n = x^(2^3) * x^(2^1) | ||
|
|
||
| Each x^(2^k) can be computed recursively; | ||
| x^(2^(k+1)) = x^(2*2^k) = x^(2^k) * x^(2^k) | ||
| """ | ||
| if n == 0: | ||
| return 1.0 | ||
| if x == 0.0: | ||
| return 0.0 | ||
| if n < 0: | ||
| # x^(-n) = (1/x)^n = 1/(x^n) (n>0) | ||
| return 1 / self.myPow(x, -n) | ||
| x_to_power_of_two = x # x^(2^0) | ||
| x_to_n = 1.0 # x^n | ||
| while n > 0: | ||
| if n % 2 == 1: | ||
| x_to_n *= x_to_power_of_two | ||
|
|
||
| # x^(2^(k+1)) = x^(2*2^k) = x^(2^k) * x^(2^k) | ||
| x_to_power_of_two = x_to_power_of_two * x_to_power_of_two | ||
| n //= 2 | ||
| return x_to_n | ||
| ``` | ||
|
|
||
| - このアプローチは、`n`を2進数として捉えるものである。例えば `n=10` (2進数で`1010`) の場合、`x^10 = x^8 * x^2` となる。 | ||
| - ループ内で`n`を右にシフト(`n //= 2`)しながら、最下位ビットが1かどうか(`n % 2 == 1`)を確認する。 | ||
| - 「2で割る」操作を「右にビットシフトする」操作に、「2で割った余りを調べる」操作を「最下位ビットを調べる」操作にそれぞれ置き換えて考えることで、`n`の各ビットを順に処理できることが納得できる。 | ||
| - ビットが1であれば、その位置に対応する`x`のべき乗(`x`, `x^2`, `x^4`, `x^8`, ...)を結果に乗算していく。 | ||
| - `x`のべき乗は、ループごとに自身を2乗することで効率的に計算される。 | ||
| - 時間計算量:`O(log(n))`。ループ回数が`n`のビット数に比例するためである。 | ||
| - 空間計算量:`O(1)`。変数の使用量が`n`の大きさに依存しないためである。 | ||
|
|
||
| ## step3 | ||
|
|
||
| ### 反復処理による実装 | ||
| ```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) | ||
| power_of_x = x | ||
| i = 0 | ||
| two_to_i = 1 | ||
| x_to_n = 1.0 | ||
| while two_to_i <= n: | ||
| if n >> i & 1: | ||
| x_to_n *= power_of_x | ||
| i += 1 | ||
| two_to_i *= 2 | ||
| power_of_x = power_of_x * power_of_x | ||
| return x_to_n | ||
| ``` | ||
|
|
||
| ### 再帰関数による実装 | ||
| ```python | ||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| if n == 0: | ||
| return 1.0 | ||
| if x == 0.0: | ||
| return 0.0 | ||
| if n == 1: | ||
| return x | ||
| if n < 0: | ||
| n = -n | ||
| x = 1 / x | ||
| return self.myPow(x, n) | ||
|
|
||
| half_n = n // 2 | ||
| remainder = n % 2 | ||
|
|
||
| return self.myPow(x, half_n) ** 2 * self.myPow(x, remainder) | ||
| ``` | ||
|
|
||
| ## step4 (FB) | ||
| ```python | ||
|
|
||
| ``` | ||
|
|
||
| # 別解・模範解答 | ||
| ```python | ||
|
|
||
| ``` | ||
|
|
||
| # 次に解く問題の予告 | ||
| - [Coin Change - LeetCode](https://leetcode.com/problems/coin-change/) | ||
| - [Binary Tree Level Order Traversal - LeetCode](https://leetcode.com/problems/binary-tree-level-order-traversal/) | ||
| - [Unique Paths - LeetCode](https://leetcode.com/problems/unique-paths/description/) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # | ||
| # @lc app=leetcode id=50 lang=python3 | ||
| # | ||
| # [50] Pow(x, n) | ||
| # | ||
|
|
||
| # @lc code=start | ||
|
|
||
|
|
||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| """ | ||
| Compute x to the power of n based on these four facts; | ||
| - x^(-n) = (1/x)^n (n>0) | ||
| - x^0 = 1 | ||
| - x^1 = x | ||
| - if n = 2*q + r, then x^(n) = (x^q)^2 * (x)^r | ||
|
|
||
| """ | ||
| if n < 0: | ||
| return self.myPow(1 / x, -n) | ||
| if n == 0: | ||
| return 1.0 | ||
| if n == 1: | ||
| return x | ||
| quotient, remainder = n // 2, n % 2 | ||
|
|
||
| return self.myPow(x, quotient) ** 2 * self.myPow(x, remainder) | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| # | ||
| # @lc app=leetcode id=50 lang=python3 | ||
| # | ||
| # [50] Pow(x, n) | ||
| # | ||
|
|
||
| # @lc code=start | ||
|
|
||
|
|
||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| """ | ||
| Compute x to the power of n by using iterative method. | ||
| Example: x=2.0 and n = 10 | ||
| n = 2^3 + 2^1, so | ||
| x^n = x^(2^3) * x^(2^1) | ||
| x^1, ..., x^(2^k) can be computed in O(k) by using this formula; x^(2^(k+1)) = x^(2*2^k) = x^(2^k) * x^(2^k) | ||
| Therefore, x^n can be also computed in O(log(n)) | ||
|
|
||
| Time Complexity: O(log(n)) | ||
| Space Complexity: O(1) | ||
| """ | ||
| if x == 0.0: | ||
| return 0.0 | ||
| if n == 0: | ||
| return 1.0 | ||
| if n < 0: | ||
| return 1 / self.myPow(x, -n) | ||
| if n == 1: | ||
| return x | ||
|
|
||
| x_to_n = 1.0 # x^n | ||
| bit_position = 0 # k | ||
| power_of_two = 1 # 2^k | ||
| x_to_power_of_two = x # x^(2^k) | ||
| while power_of_two <= n: | ||
| if (n >> bit_position) & 1: | ||
| x_to_n *= x_to_power_of_two | ||
| bit_position += 1 | ||
| x_to_power_of_two = x_to_power_of_two * x_to_power_of_two | ||
| power_of_two = 2 * power_of_two | ||
|
|
||
| return x_to_n | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # | ||
| # @lc app=leetcode id=50 lang=python3 | ||
| # | ||
| # [50] Pow(x, n) | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| """ | ||
| Compute x to the power of n using doubling. | ||
| Example: x=2.0, n = 10 | ||
| n = 2^3 + 2^1 | ||
| x^n = x^(2^3) * x^(2^1) | ||
|
|
||
| Each x^(2^k) can be computed recursively; | ||
| x^(2^(k+1)) = x^(2*2^k) = x^(2^k) * x^(2^k) | ||
| """ | ||
| if n == 0: | ||
| return 1.0 | ||
| if x == 0.0: | ||
| return 0.0 | ||
| if n < 0: | ||
| # x^(-n) = (1/x)^n = 1/(x^n) (n>0) | ||
| return 1 / self.myPow(x, -n) | ||
| x_to_power_of_two = x # x^(2^0) | ||
| x_to_n = 1.0 # x^n | ||
| while n > 0: | ||
| if n % 2 == 1: | ||
| x_to_n *= x_to_power_of_two | ||
|
|
||
| # x^(2^(k+1)) = x^(2*2^k) = x^(2^k) * x^(2^k) | ||
| x_to_power_of_two = x_to_power_of_two * x_to_power_of_two | ||
| n //= 2 | ||
| return x_to_n | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # | ||
| # @lc app=leetcode id=50 lang=python3 | ||
| # | ||
| # [50] Pow(x, n) | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| if n == 0: | ||
| return 1.0 | ||
| if x == 0.0: | ||
| return 0.0 | ||
| if n == 1: | ||
| return x | ||
| if n < 0: | ||
| n = -n | ||
| x = 1 / x | ||
| return self.myPow(x, n) | ||
|
|
||
| half_n = n // 2 | ||
| remainder = n % 2 | ||
|
|
||
| return self.myPow(x, half_n) ** 2 * self.myPow(x, remainder) | ||
|
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. remainder で場合分けした方が素直かなと個人的には思います。
Owner
Author
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. レビューありがとうございます。READMEで
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. 数式上はそうですが、結局 r は 0, 1 のみで、1 の場合は追加で x を掛ける、0 の場合は追加で掛けない、というのが繰り返し二乗法のポイントで、この関数の仕事として場合分けした方が見やすいと思っています。 |
||
|
|
||
|
|
||
| # @lc code=end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # | ||
| # @lc app=leetcode id=50 lang=python3 | ||
| # | ||
| # [50] Pow(x, n) | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def myPow(self, x: float, n: int) -> float: | ||
| if n == 0: | ||
| return 1 | ||
| if n < 0: | ||
| return self.myPow(1 / x, -n) | ||
| power_of_x = x | ||
| i = 0 | ||
|
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. i を使わなくても書けるでしょうか?two_to_i が同様の情報を持っていそうです。
Owner
Author
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. 確かに の部分を としても動くので、 |
||
| two_to_i = 1 | ||
| x_to_n = 1.0 | ||
| while two_to_i <= n: | ||
| if n >> i & 1: | ||
| x_to_n *= power_of_x | ||
| i += 1 | ||
| two_to_i *= 2 | ||
| power_of_x = power_of_x * power_of_x | ||
| return x_to_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. 個人的に x_to_n が x^n を意味するように読めませんでした。英語的には x to the n だと思いますが、省略しても伝わるかわかりませんでした。
Owner
Author
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. レビューありがとうございます。
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. 2のべき乗は、n を二進数で表した時にある桁が 1 であるかを見ているので bit とすることもありだと思いました。
Owner
Author
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. 確かにビットシフトして計算しているのが伝わりやすい気がしました。ありがとうございます。 |
||
|
|
||
|
|
||
| # @lc code=end | ||
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.
divmod を使うこともできますね。
https://docs.python.org/3.13/library/functions.html#divmod
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.
コメントありがとうございます。
「こんなのもあるよ」と教えてくださったのだとは思いますが、一応補足しますと、自分の以前のPR( #10 (comment) )のレビューにて、
divmodが読みにくい、見慣れないというコメントがあったので、自分は//と%とで書くように心がけています。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.
はい、どちらでも良いと思います!