Skip to content
Open
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
194 changes: 194 additions & 0 deletions 6_zigzag-conversion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# 6. Zigzag Conversion

## 1st

### ①

ジグザグに動いて作った2次元のリストを1つの文字列に結合する。

所要時間: 8:55

n: len(s)
- 時間計算量: O(n)
- 空間計算量 (auxiliary): O(n)

```py
class Solution:
def convert(self, s: str, numRows: int) -> str:
assert numRows > 0
if numRows == 1:
return s
zigzag_rows = [[] for _ in range(numRows)]
row = 0
direction = -1
for c in s:
zigzag_rows[row].append(c)
if row == 0:
direction = 1
elif row == numRows - 1:
direction = -1
Comment on lines +26 to +29

Choose a reason for hiding this comment

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

direction = -1 で初期化している場合、

            if row == 0 or row == numRows - 1:
                direction = -direction

でも書けそうですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

その書き方も迷ったんですよね...どちらでも良いと思いましたが、反転させるやり方だと今のdirectionがどっちなのか一応意識しないとダメかなと思って選ばなかった感じでした。自分の書いた方だと最上段に行ったら下に、最下段に行ったら上に行くことが分かりやすいかもと思い。

row += direction
return ''.join(map(lambda row: ''.join(row), zigzag_rows))
```

### ②

計算でどの位置が何行目に属するかを求める。

なお、zigzag_rowsにappendするリストやformer, latterをgeneratorに変えようとしたところ、異なる答えになった。

たとえば、入力: s = "PAYPALISHIRING", numRows = 4 で
出力: "PINYAHRYAHRPI", 期待する出力: "PINALSIGYAHRPI"
のように真ん中辺りの文字列だけ異なる。

おそらく遅延評価されてループ内のformer, latterで参照するiがnumRows-2になっているのだと思われる。


所要時間: 24:42

n: len(s), m: numRows
- 時間計算量: O(mn)
- 空間計算量 (auxiliary): O(n)

```py
class Solution:
def convert(self, s: str, numRows: int) -> str:
assert numRows > 0
if numRows == 1:
return s
zigzag_rows = []
# nr: numRows
# 0, 2nr-2, 2*(2nr-2), ...
zigzag_rows.append([c for i, c in enumerate(s) if i % (2 * numRows - 2) == 0])
for i in range(1, numRows - 1):
# i, 2nr-2+i, 2*(2nr-2)+i, ...
former = [c for j, c in enumerate(s) if j % (2 * numRows - 2) == i]
# 2nr-2-i, 2*(2nr-2)-i, ...
latter = [c for j, c in enumerate(s) if j % (2 * numRows - 2) == 2 * numRows - 2 - i]
row_elements = chain.from_iterable(zip_longest(former, latter, fillvalue=''))
zigzag_rows.append(list(row_elements))
# nr-1, (2nr-2)+nr-1, 2*(2nr-2)+nr-1, ...
zigzag_rows.append([c for i, c in enumerate(s) if i % (2 * numRows - 2) == numRows - 1])
return ''.join(map(lambda row: ''.join(row), zigzag_rows))
```

### ③

②をもう少し効率化したバージョン。
`for i in range(1, numRows - 1)` のiはrowとして、中のループ変数をjじゃなくiにした方が間違いにくいかもしれない。

所要時間: 3:55

n: len(s)
- 時間計算量: O(n)
- 空間計算量 (auxiliary): O(n)

```py
class Solution:
def convert(self, s: str, numRows: int) -> str:
assert numRows > 0
if numRows == 1:
return s
zigzag_chars = []
cycle = 2 * numRows - 2
for i in range(0, len(s), cycle):
zigzag_chars.append(s[i])
for i in range(1, numRows - 1):
for j in range(i, len(s), cycle):
zigzag_chars.append(s[j])
next_j = j + cycle - 2 * i
if next_j < len(s):
zigzag_chars.append(s[next_j])
for i in range(numRows - 1, len(s), cycle):
zigzag_chars.append(s[i])
return ''.join(zigzag_chars)
```


## 2nd

### 参考

- https://discord.com/channels/1084280443945353267/1196472827457589338/1248961797457842196
- https://github.com/Mike0121/LeetCode/pull/26

行って戻る、をループの中で繰り返す。

```py
class Solution:
def convert(self, s: str, numRows: int) -> str:
assert numRows > 0
if numRows == 1:
return s

Choose a reason for hiding this comment

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

こっちの書き方であれば、numRows == 1のケースを特別扱いしなくても良さそうですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

あーたしかにこの実装だとそうなりますね...ありがとうございます 🙏

zigzag_rows = [[] for _ in range(numRows)]
i = 0

Choose a reason for hiding this comment

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

i, jはもう少しわかりやすくできる気もしました。i -> index, j -> rowとかですかね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

i -> indexで分かりやすくなるのか自分ははっきりとは分からなかったですが、j -> rowはたしかにと思いました

while i < len(s):
for j in range(numRows):
if i >= len(s):
break
zigzag_rows[j].append(s[i])
i += 1
for j in range(numRows - 2, 0, -1):
if i >= len(s):
break
zigzag_rows[j].append(s[i])
i += 1
return ''.join(map(lambda row: ''.join(row), zigzag_rows))
```

- https://discord.com/channels/1084280443945353267/1233603535862628432/1233762537598877750
- https://github.com/goto-untrapped/Arai60/pull/5
- https://discord.com/channels/1084280443945353267/1201211204547383386/1232754805907132556
- https://github.com/shining-ai/leetcode/pull/60

③の別バージョン。
ループ内のrowはスコープが嫌な感じだが、 `row = position if position < numRows else cycle - position` と三項演算子で書くのもきれいな感じにならず仕方なくこちらで書いた。

```py
class Solution:
def convert(self, s: str, numRows: int) -> str:
assert numRows > 0
if numRows == 1:
return s
zigzag_rows = [[] for _ in range(numRows)]
cycle = 2 * numRows - 2
for i in range(len(s)):
position = i % cycle
if position < numRows:
row = position
else:
row = cycle - position
zigzag_rows[row].append(s[i])
return ''.join(map(lambda row: ''.join(row), zigzag_rows))
```

- https://discord.com/channels/1084280443945353267/1225849404037009609/1229834874823774278
- https://github.com/SuperHotDogCat/coding-interview/pull/4
- https://discord.com/channels/1084280443945353267/1200089668901937312/1225104265933230101
- https://github.com/hayashi-ay/leetcode/pull/71
- https://discord.com/channels/1084280443945353267/1217527351890546789/1218854624237326338
- https://github.com/cheeseNA/leetcode/pull/4


## 3rd

自分的に一番素直な感じがしたのでこれを3rdにした。

```py
class Solution:
def convert(self, s: str, numRows: int) -> str:
assert numRows > 0
if numRows == 1:
return s
Comment on lines +181 to +182

Choose a reason for hiding this comment

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

len(s) <= numRow の場合にもそのまま s を返してあげると余計な計算が発生しないのでよさそうです。

zigzag_rows = [[] for _ in range(numRows)]
row = 0
direction = 1
for c in s:
if row == 0:
direction = 1
elif row == numRows - 1:
direction = -1
zigzag_rows[row].append(c)
row += direction
return ''.join(map(lambda row: ''.join(row), zigzag_rows))

Choose a reason for hiding this comment

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

いいと思います。
最後のreturnだけ少し気になっているところで, 自分が昔書いた時は
return "".join(row_char for row_string in row_strings for row_char in row_string)
と書いていました。これに比べたらまだ
return ''.join(map(lambda row: ''.join(row), zigzag_rows))
の方が読みやすい気もしますが, これでも認知負荷がかかるなら普通にfor文で書いてしまってもいい気がしています。
これぐらいだったら書いても大丈夫なのかな?(コメントあった方が分かりやすい気も, します)

Copy link

Choose a reason for hiding this comment

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

個人的には for 文でストレートフォワードに書いたほうが読みやすいように感じました。

Choose a reason for hiding this comment

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

map(lambdaよりは個人的には内包表記の方が好きですが、内包表記は結構好みが分かれる気はします。
普通に書いた方がわかりやすいですかね。

return ''.join([''.join(row) for row in zigzag_rows])

あとは、zigzag_rows: list[list[str]]ではなくてzigzag_rows: list[str]にして、rowにappendせずに文字列を入れておくというのもあるかなと思います。その方が全体的にちょっと読みやすくなるかもしれません。

Copy link

Choose a reason for hiding this comment

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

[] なくても、Generator が返るので動きます。
''.join(''.join(row) for row in zigzag_rows)

Copy link
Owner Author

Choose a reason for hiding this comment

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

map, 結構分かりにくいですかね。(lambdaという文字列が長いのは微妙に思います)
内包表記的に自分が書くなら、中間状態でメモリを使うlistより generator expressionにしそうです。
for文で作るのも同様にメモリ使いそうですね。もちろんユースケースにも依りますが、今回のmapくらいなら可読性の低下よりメモリ効率取っていいんじゃないか?という感覚でした。

rowにappendせずに文字列を入れておく

これは文字列がimmutableだから、CPythonの最適化を考えなければ文字列の構築にコストがかかる気がしました (何か勘違いしてたらすみません)。

Copy link
Owner Author

Choose a reason for hiding this comment

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

(generatorの話被っちゃいました)

Choose a reason for hiding this comment

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

Generatorはそうですね。文字列の構築も、多分id(等)のlistを保持するより高コストなんじゃないかなと思います。
(Leetcodeのテストケースぐらいでは初期化の方が支配的でした)

このコメントの趣旨は、関数のネストが少なくなるので、ちょっと読みやすくなるかも?ぐらいでした。

return ''.join(zigzag_rows)

```