Skip to content

779. K-th Symbol in Grammar#10

Open
atmaxstar wants to merge 1 commit into
mainfrom
779
Open

779. K-th Symbol in Grammar#10
atmaxstar wants to merge 1 commit into
mainfrom
779

Conversation

@atmaxstar
Copy link
Copy Markdown
Owner

n行k番目のシンボルはn-1行{(k+1)//2}番目をもとに決まり、kが奇数の時はn-1行目のシンボルと一緒、kが偶数の時はn-1行目のシンボルと反対するという性質をもとにn=nからn=1までloopで辿り、n=1の0と一緒か反対かを求めて返している。これでもいいと思うが、k番目の数というのをcolで表していて個人的にこれはn×mの二次元配列に使うべきで、今回のようにnの数によって長さが変わる文字列に対してインデックスをcolと名付けると誤解を生じやすいと感じた。kでいい気がする。

https://github.com/hayashi-ay/leetcode/pull/46/changes
この人は最初に入力サイズを見て制約を考えていたので、自分も参考にして制約を考えてみる。1 <= n <= 30, 1 <= k <= 2^(n-1)ということで、愚直にnth rowまでの文字列を展開してk番目の文字を線形探索すると考えると、PythonのUTF-8文字列で '0' や '1' は 1 byteなので2^29byte≒1000^3/2=500MB。競プロでは250MBあたりがボーダーらしいので全ての文字を展開するのは現実的ではなさそう。線形探索も2^29≒10^9/2で、CPUが1秒に10^9回の単純演算を行うことができるのを考慮するとマックスで0.5秒で判断できる。これを考慮するとこの人がMemory Limit Exceedになったのが納得できる。
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のUTF-8文字列で '0' や '1' は 1 byteなので

PythonのstrはUTF-8を使っていますか?

Copy link
Copy Markdown
Owner Author

@atmaxstar atmaxstar May 10, 2026

Choose a reason for hiding this comment

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

https://peps.python.org/pep-0393/
ここを見るとASCII文字・BMP文字・絵文字など、文字列に含まれる最大の Unicode code point に応じてサイズが変わるみたいです。今回の場合は0, 1のみなので1文字につき1byteの固定長配列として保存されます。

ChatGPTには
「UTF-8というより、「ASCII範囲の Unicode code point を1 byte表現で持っている」と言うのが正確」と言われました。

n行k番目のシンボルはn-1行{(k+1)//2}番目をもとに決まり、kが奇数の時はn-1行目のシンボルと一緒、kが偶数の時はn-1行目のシンボルと反対するという性質をもとにn=nからn=1までloopで辿り、n=1の0と一緒か反対かを求めて返している。これでもいいと思うが、k番目の数というのをcolで表していて個人的にこれはn×mの二次元配列に使うべきで、今回のようにnの数によって長さが変わる文字列に対してインデックスをcolと名付けると誤解を生じやすいと感じた。kでいい気がする。

https://github.com/hayashi-ay/leetcode/pull/46/changes
この人は最初に入力サイズを見て制約を考えていたので、自分も参考にして制約を考えてみる。1 <= n <= 30, 1 <= k <= 2^(n-1)ということで、愚直にnth rowまでの文字列を展開してk番目の文字を線形探索すると考えると、PythonのUTF-8文字列で '0' や '1' は 1 byteなので2^29byte≒1000^3/2=500MB。競プロでは250MBあたりがボーダーらしいので全ての文字を展開するのは現実的ではなさそう。線形探索も2^29≒10^9/2で、CPUが1秒に10^9回の単純演算を行うことができるのを考慮するとマックスで0.5秒で判断できる。これを考慮するとこの人がMemory Limit Exceedになったのが納得できる。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nth rowまでの文字列を展開してk番目の文字を線形探索

strのk番目の文字を確認するのに線形探索が必要でしょうか?

Copy link
Copy Markdown
Owner Author

@atmaxstar atmaxstar May 10, 2026

Choose a reason for hiding this comment

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

普通に文字列[k-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.

もしUTF-8のようにvariable-length encodingだったら、確かに線形探索が必要でしたね。

n行k番目のシンボルはn-1行{(k+1)//2}番目をもとに決まり、kが奇数の時はn-1行目のシンボルと一緒、kが偶数の時はn-1行目のシンボルと反対するという性質をもとにn=nからn=1までloopで辿り、n=1の0と一緒か反対かを求めて返している。これでもいいと思うが、k番目の数というのをcolで表していて個人的にこれはn×mの二次元配列に使うべきで、今回のようにnの数によって長さが変わる文字列に対してインデックスをcolと名付けると誤解を生じやすいと感じた。kでいい気がする。

https://github.com/hayashi-ay/leetcode/pull/46/changes
この人は最初に入力サイズを見て制約を考えていたので、自分も参考にして制約を考えてみる。1 <= n <= 30, 1 <= k <= 2^(n-1)ということで、愚直にnth rowまでの文字列を展開してk番目の文字を線形探索すると考えると、PythonのUTF-8文字列で '0' や '1' は 1 byteなので2^29byte≒1000^3/2=500MB。競プロでは250MBあたりがボーダーらしいので全ての文字を展開するのは現実的ではなさそう。線形探索も2^29≒10^9/2で、CPUが1秒に10^9回の単純演算を行うことができるのを考慮するとマックスで0.5秒で判断できる。これを考慮するとこの人がMemory Limit Exceedになったのが納得できる。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CPUが1秒に10^9回の単純演算を行うことができるのを考慮するとマックスで0.5秒で判断できる

クロック数のことを指しているのなら、1GHzだと、かなり遅くないですかね?

また、クロック数を直接Pythonの実行時間の見積もりに使うのは、飛躍がありそうに思いました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

今回目安に10^9を持ってきましたが秒数を導き出すのは難しそうですね。最初の方針を思いつく段階ではオーダー記法のみに言及するようにします。

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の場合は 10^7steps / secでざっくり考えています。総計算ステップ数が10^9/2であれば50 sくらいでしょうか

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

50sもかかるんですね...目安を教えてくださりありがとうございます🙏

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#16

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます!10^6〜10^7/secで覚えておきます!

return (k - 1)%2
else:
#10
return k%2
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(k - 1)%2 k%2だと、読み手が頭の中で計算しないと読めなさそうですかね?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ネストを深くしたくなかったのでこう書いたのですが可読性的によくないですね。

converted = "01"
index = (k-1) % 2
return converted[index]

とかにしておけばよかったですね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

それだと文字列を返していますね。index = (k-1) % 2も少し唐突かなと感じました。

return 1 if k % 2 == 1 else 0みたいにするのが、分かりやすいと思います。

previous_val = self.kthGrammar(n - 1, (k + 1)//2)
if previous_val == 0:
#01
return (k - 1)%2
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

演算子の周りのスペースの空け方に一貫性がないのが気になりました。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

+, -は前後にスペースを入れて、/, *は全部詰めてたんですけどPEP8的には優先される演算子に関してはスペースなし、優先されない演算子にはスペースを開けるように書かれてました。
https://pep8-ja.readthedocs.io/ja/latest/#id15:~:text=%23%20%E6%AD%A3%E3%81%97%E3%81%84%3A%0Ai,%2D%20b)

# 正しい:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# 間違い:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)

なのでこの場合は(k+1) // 2, return k % 2とかにした方が良さそうですね

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

念のため、これは全部開けても全部空けなくてもいいが、優先度の高いところが空けたらそこよりも優先度の低いところは空けろということかと思います。

## step2:
### code
```python
class Solution:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

再帰で書かれていますが、ループでも書いてみるのはどうでしょうか

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

class Solution:
    def kthGrammar(self, n: int, k: int) -> int:
        ks = []
        for _ in range(1, n):
            ks.append(k)
            k = (k + 1) // 2
        
        val = 0
        for k in reversed(ks):
            if val == 0:
                val = (k - 1) % 2
            else:
                val = k % 2
        return val

この解法だと空間計算量がO(N)かかりますけど1 <= n <= 30なので特に問題はないと思います。nが1000を超えそうなあたりから再帰でなくループを検討するのが良さそうですね。

Copy link
Copy Markdown

@liquo-rice liquo-rice May 11, 2026

Choose a reason for hiding this comment

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

フリップした回数を数えると良いので、k = 1から始めなくてもできますね。

Comment on lines +26 to +28
他の人のコードを見てみる。
https://github.com/kitano-kazuki/leetcode/pull/47/changes
n行k番目のシンボルはn-1行{(k+1)//2}番目をもとに決まり、kが奇数の時はn-1行目のシンボルと一緒、kが偶数の時はn-1行目のシンボルと反対するという性質をもとにn=nからn=1までloopで辿り、n=1の0と一緒か反対かを求めて返している。これでもいいと思うが、k番目の数というのをcolで表していて個人的にこれはn×mの二次元配列に使うべきで、今回のようにnの数によって長さが変わる文字列に対してインデックスをcolと名付けると誤解を生じやすいと感じた。kでいい気がする。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

コードとは関係ない & もし意図してのことでしたら恐縮ですが,markdownでは2行分改行しないとレンダリング時に改行されません.

if n == 1:
return 0
previous_val = self.kthGrammar(n - 1, (k + 1)//2)
if previous_val == 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.

個人的には「偶奇によってprivious_valを反転するか決める」という意味を反映させた

if k % 2 == 0:
    return 1 - previous_val
else:
    return previous_val

という書き方が好みです.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これは流石に賛否両論ありそうですが,説明コメントを1行入れた上で

return (k + 1 - previous_val) % 2

とかも1行でかけてシンプルかなと思います.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants