-
Notifications
You must be signed in to change notification settings - Fork 0
779. K-th Symbol in Grammar #35
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
Open
ryosuketc
wants to merge
1
commit into
main
Choose a base branch
from
779_k_th_symbol_in_grammar
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # 779. K-th Symbol in Grammar | ||
|
|
||
| https://leetcode.com/problems/k-th-symbol-in-grammar/ | ||
|
|
||
| ## Comments | ||
|
|
||
| ### step1 | ||
|
|
||
| * recursion の問題だなーとは思いながら考えたけど明確な解法思いつかず。 | ||
| * とりあえず愚直な解法を実装してみたけど memory limit exceeded | ||
| * n が 30 までということは、2^30 文字の文字列を保存することになる。 | ||
| * `sys.getsizeof("a") -> 50` (bytes) とかなので、途方もないサイズになる | ||
| * https://docs.python.org/3/library/sys.html#sys.getsizeof | ||
| * LeetCode によると binary search tree と recursion の解法があるみたい。ただ説明が長いので細かく見ずに一旦他の人の解答を探る。 | ||
| * https://github.com/fuga-98/arai60/pull/46/files | ||
|
|
||
| * ビットを使った解法 | ||
| * あービットによる解法あったのか。以前解いたときは、ビットで解けそうだなくらいの感覚だったんだけど、今回は出てこなかった。 | ||
| * https://github.com/olsen-blue/Arai60/pull/47#discussion_r2002307405 | ||
| * > あ、この問題もっとマクロな話をするとビットカウントの偶奇になっていますよ。二分木を考えて、右に行くとビットが反転して、左に行くとしないということですね。 | ||
| * https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.adit16u7jkla | ||
| * `int.bitcount()` | ||
| * https://docs.python.org/3/library/stdtypes.html#int.bit_count | ||
| * 知らんかった。bin(int).count() でも代用はできる | ||
| * > kの値にどう結びつくのかイメージできずでしたが、k-1 の二進数表記はルートからの移動パターンなんですね。k=5 (k-1=4, 二進数 100)だと、右(1)->左(0)->左(0)ですね。なので、k-1のビットを数えて偶奇を調べれば良いんですね。 | ||
|
|
||
| ``` | ||
| 0 | ||
| 01 | ||
| 0110 | ||
| 01101001 | ||
| ... | ||
| ``` | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| return (k - 1).bit_count() & 1 | ||
| ``` | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| if n == 1: | ||
| return 0 | ||
| # k % 2 == 0 (偶数) のときビット反転、奇数のとき反転しない | ||
| return self.kthGrammar(n - 1, (k + 1) // 2) ^ (k % 2 == 0) | ||
| ``` | ||
|
|
||
| ``` | ||
| self.kthGrammar(2, 1) -> call self.kthGrammar(1, 1) | ||
| self.kthGrammar(1, 1) -> return 0 | ||
| ``` | ||
|
|
||
| ``` | ||
| self.kthGrammar(2, 2) -> call self.kthGrammar(1, 1) | ||
| self.kthGrammar(1, 1) -> return 0 | ||
| ``` | ||
|
|
||
| * https://github.com/olsen-blue/Arai60/pull/47/files | ||
| * > kの偶奇が大事そう?? 子ノード(n, k)を考えると、kが奇数の時、その親ノードと同じ値になる。kが偶数の時、その親ノードから反転した値になる。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| if n == 1: | ||
| return 0 | ||
|
|
||
| if k % 2: | ||
|
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. 否定してから int にキャストしているのが、やや分かりずらく感じました。引き算のほうがシンプルだと思いました。 if k % 2:
return self.kthGrammar(n - 1, (k + 1) // 2)
else:
return 1 - self.kthGrammar(n - 1, (k + 1) // 2) |
||
| return int(self.kthGrammar(n - 1, (k + 1) // 2)) | ||
| else: | ||
| return int(not self.kthGrammar(n - 1, (k + 1) // 2)) | ||
| ``` | ||
|
|
||
| ### step2 | ||
|
|
||
| * https://leetcode.com/problems/k-th-symbol-in-grammar/editorial/ | ||
| * ここまで読んでイマイチ腹落ちしないので、割と丁寧そうな LeetCode の解法を改めて見てみる。 | ||
| * Binary Search を素直に実装した `Solution1` ふつうにわかりやすい。LeetCode のをきれいに書き直した。 | ||
| * 前半分、後ろ半分でビットが入れ替わっていることを利用した `Solution2`。これもわかりやすい。 | ||
| * 0 <-> 1 の反転には色々方法が。`1 - x` とか `1 ^ x` とか。 | ||
| * k - 1 のビット数カウント | ||
| * k から、row のノード数の半分を引いていき、最終的に 1 になるまで繰り返す | ||
| * `k - 2^a - 2^b - ... = 1` | ||
| * `k - 1 = 2^a + 2^b...` | ||
| * > The number of bits in number k is logk. | ||
| * 一応この説明でなんとなくはわかるのだが、 | ||
| * なぜ `k - 1` なのか | ||
| * zero index になおしているのはそうだけど、なぜ k の偶奇で答えがわかるのか。n は? | ||
| * 結局ビットが 1 である回数 == 反転 (flip) した回数? | ||
|
|
||
| ### step3 | ||
|
|
||
| * ビットの解法が腹落ちしきらないので一旦納得できた `Solution2` のパターンで練習 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # memory limit exceeded | ||
| class SolutionMLE: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| row = '0' | ||
| for i in range(n): | ||
| next_row = [] | ||
| for char in row: | ||
| if char == '0': | ||
| next_row.append('01') | ||
| elif char == '1': | ||
| next_row.append('10') | ||
| row = ''.join(next_row) | ||
| return int(''.join(row)[k - 1]) | ||
|
|
||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| class Solution1: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| def kth_grammer_helper(n, k, root_val): | ||
| if n == 1: | ||
| return root_val | ||
| total_nodes = 2 ** (n - 1) | ||
| half_of_total_nodes = total_nodes // 2 | ||
| # Target node will be in the right half -> reverse | ||
| if k > total_nodes / 2: | ||
| return kth_grammer_helper(n - 1, k - half_of_total_nodes, root_val ^ 1) | ||
| # Target node will be in the left half -> not reverse | ||
| return kth_grammer_helper(n - 1, k, root_val) | ||
|
|
||
| return kth_grammer_helper(n, k, 0) | ||
|
|
||
|
|
||
| class Solution2: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| def kth_grammer_helper(n, k): | ||
| if n == 1: | ||
| return 0 | ||
| total_nodes = 2 ** (n - 1) | ||
| half_of_total_nodes = total_nodes // 2 | ||
| # Target node will be in the right half, | ||
| # 1. move to the left half (k - half_of_total_nodes), | ||
| # 2. reverse (1 ^ ...), | ||
| # 3. and go up (n - 1). | ||
| if k > half_of_total_nodes: | ||
| return 1 ^ kth_grammer_helper(n - 1, k - half_of_total_nodes) | ||
| # Target node will be in the left half -> not reverse. Just go up (n - 1) | ||
| return kth_grammer_helper(n - 1, k) | ||
|
|
||
| return kth_grammer_helper(n, k) | ||
|
|
||
|
|
||
| class Solution3: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| return (k - 1).bit_count() & 1 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| class Solution: | ||
| def kthGrammar(self, n: int, k: int) -> int: | ||
| def kth_grammer_helper(n, k): | ||
|
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. 引数が同じなのでインナー関数を定義する必要はないと思います。 |
||
| if n == 1: | ||
| return 0 | ||
| total_nodes = 2 ** (n - 1) | ||
| half_of_total_nodes = total_nodes // 2 | ||
| # Target node will be in the right half -> flip, | ||
| if k > half_of_total_nodes: | ||
| return 1 ^ kth_grammer_helper(n - 1, k - half_of_total_nodes) | ||
| # Target node will be in the left half -> not flip. | ||
| return kth_grammer_helper(n - 1, k) | ||
|
|
||
| return kth_grammer_helper(n, k) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 のオブジェクトに必要なメモリ + str に必要なメモリで 50 バイトとなっているのだと思います。文字一文字あたりは 1 バイトのようです。