Skip to content

Reverse Linked List #8

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
wants to merge 1 commit into
base: main
Choose a base branch
from
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
203 changes: 203 additions & 0 deletions reverseLinkedList.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Reverse Linked List

## step1

- 問題を整理する
- リンクドリストが与えられるので逆にして返せ
- 手でやるならどうやるか
- 前から読んでってvalをstackに書いておく
- 最後まで読み終わったらstackの後ろからvalを取り出してLinked Listを繋げていく
- コードで書いてみる
- 5分ほどで解けた
- 最初に書いたとき`while node.next is not None:`としてしまった
- nodeの次がなくなったら次に進めないなという感覚があったから
- 実際はnode.next is Noneでストップしてしまうと最後の要素をカウントできない

```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None:

Choose a reason for hiding this comment

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

headを特別扱いしなくても良さそうです

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにその通りです。
あまり考えずにとりあえずという感じで書いていました。

return None

stack = list()
Copy link

@Mike0121 Mike0121 Oct 26, 2024

Choose a reason for hiding this comment

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

質問ですが、listを[]ではなく、list()として定義した意図はありますか?

自分も気になって調べましたが、
stack = []の方が関数呼び出しが行われない分自然みたいです。
https://stackoverflow.com/questions/33716401/whats-the-difference-between-list-and

Copy link

Choose a reason for hiding this comment

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

たしか辞書の初期化も dict() より = {} の方が推奨と聞いたことがあります
https://stackoverflow.com/questions/17097985/dict-vs-in-python-which-is-better
https://switowski.com/blog/dict-function-vs-literal-syntax/

node = head

while node is not None:
stack.append(node.val)
node = node.next

dummy = ListNode(-1)

Choose a reason for hiding this comment

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

スタックから初期ノードを持ってくれば、ダミーが不要になってコードがシンプルになりそうですね

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにそうですね、思いつきませんでした。ありがとうございます。

reverse_node = dummy

while stack:
val = stack.pop()
next_node = ListNode(val)
reverse_node.next = next_node
reverse_node = reverse_node.next

return dummy.next
```

## step2 他の人の解法を見る・自分の修正


### [tarinaiさん](https://github.com/tarinaihitori/leetcode/pull/6/files/7ae6c941f485e4fd52fab64cf17584150ea87d03#diff-f1530fc1072ee1f0b7de99a2e5236992c72355da69982c8ca516fcfba7c57927)
- リストを読んでるときに逆向きにつけ直している
- こっちの方が一周で終わるから早い
- 自分のやり方と考え方から違って面白い
- currentとpreviousという名前の付け方について議論している
- [ここ](https://github.com/colorbox/leetcode/pull/22#discussion_r1694239660)でも
- 今のと前のは確かに違和感がある
- reversed と not reversedのように操作を終えたもの、操作前のもののような名付けは直感に合う
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
current = head
previous = None
while current:
next_temp = current.next
current.next = previous
previous = current
current = next_temp
return previous
```

### 再帰

- 再帰でもできるらしいので自分で考えてみる
- 再帰で考えたことがないのでどのようなところから取り組めばいいのかわからない
- 手でやる場合
- 一つのリンクドリストに一人が担当しているとする
- 前の人から信号を後ろの人に渡す
- 後ろの人がいなかったらリンクを逆向きにして前に返す
- 後ろの人から信号が来たらリンクを逆にして前に返す
- よくわからないので他の人の回答を見てそれを解釈する

- 解釈

```python
class Solution:
# leetcodeはデフォルトの再帰の深さの最大数が55,000のため、今回の制約(最大5,000)だとRecursionErrorは考慮しなくて良い
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def _reverse_linked_list(head, previous):
Copy link

Choose a reason for hiding this comment

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

inner function の名前を _ で始めることは少ないように思います。

_ で始めるのは、メンバ関数のうち non-public なものが多いと思います。
https://peps.python.org/pep-0008/#method-names-and-instance-variables

Use one leading underscore only for non-public methods and instance variables.

# head = 次に処理するnode
# previous = すでに処理したnode
if head is None:
return previous
# 次に処理するnodeがなくなっていればすでに処理したnodeを返す
temp = head.next
# 次に処理するnodeの次のnodeを取得する
head.next = previous
# head.nextをすでに処理したnodeに繋げる
return _reverse_linked_list(temp, head)
# tempを次に処理するnode、headをすでに処理したnodeとして次の操作に移行する
return _reverse_linked_list(head, None)
# 一番最初は次に処理するのはheadですでに処理したnodeは存在しない
```
- 仕組みはわかったがどうやったらこの再帰を思いつけるだろう
- 手でやるなら
- 何人かで鎖のようなものを繋ぎかえる
- 前の人からは繋ぎかえたもの最後と繋ぎかえてないものの先頭を渡される
- 繋ぎかえてないものの先頭を外して、繋ぎかえたものの最後に繋げる
- 次の人に渡す
- 繋ぎかえてないものがなくなったら繋ぎかえたものを返す
- RecursionErrorとは
- 再帰が深すぎる時に起きるエラー
- [sys.setrecursionlimit()](https://docs.python.org/3/library/sys.html#sys.setrecursionlimit)で変更できる

```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head is None or head.next is None:
return head
new_head = self.reverseList(head.next)
# 最後の要素をとってくる
head.next.next = head
head.next = None
# 繋いでる向きを反対にする
return new_head
```
- 手でやるなら
- 何人かで鎖のようなものを繋ぎかえる
- 最初に鎖の担当する箇所を決める
- 担当箇所が鎖の最後ならそれを前の人に戻す
- 戻されたら自分の箇所を逆向きにつなぐ
- 鎖の最後の部分を前の人に戻す
- この時自分の操作した部分は最後の部分に連なっている
- 最初の人まで戻ってきたら最初の向きを繋ぎかえて鎖の最後を返す
- わかりにくい説明になってしまった。
- 鎖全体のheadと自分の担当箇所のheadが存在するが同じ単語で表されているところがコードの難しさに繋がっている気がする
- わかりやすいように変えると次のようになるか?
```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverseListHelper(previous):
if previous is None or previous.next is None:
return previous

last_node = reverseListHelper(previous.next)

previous.next.next = previous
previous.next = None
return last_node

return reverseListHelper(head)
```

[Odaさんの再帰の考え方](https://discord.com/channels/1084280443945353267/1231966485610758196/1239417493211320382)
>再帰というのは、リストの頭を担当する部下が、何かを下流にお願いして、仕事が返ってきて、自分も何かをすると、全体のリストが逆順になるというものです。
>下流に、自分よりも下流の部分を逆順にしろ、といって、したよ。といって、ぽいっと渡されても困りますね。その時に、自分が効率的に仕事をするために何を教えてほしいですか。

- 効率的に仕事をするには何を教えて欲しいかという視点を持っておきたい

### 自分の修正

- stackを使うより一周しながらリンクを繋ぎ直す方がわかりやすいと思ったのでそちらでやる
- 自分で書いてみるとnext_tempが思いつきづらいと思った

```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
reversed = None
not_reversed = head

while not_reversed:
next_temp = not_reversed.next
not_reversed.next = reversed

reversed = not_reversed
not_reversed = next_temp
Comment on lines +164 to +169

Choose a reason for hiding this comment

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

next_tempが思いつきづらいとありましたが、
ここは書こうと思えば、
not_reversed.next, reversed, not_reversed = reversed, not_reversed, not_reversed.next
みたいにnext_tempを使わずに1行で書けます。
たださすがに可読性が悪すぎるので、一時的にnext_tempに退避させていると考えたら
すっと思いつけるのではないかと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

その考え方はなかったのでアドバイス頂けてありがたいです。


return reversed
```

`while not_reversed` ではなく`while not_reversed is not None`と書くべきなのでは?

- is not None について
- Googleは[Always use "is None"(or "is not None")](https://google.github.io/styleguide/pyguide.html#2144-decision)と言っている
- pep8は[beware of writing](https://peps.python.org/pep-0008/#programming-recommendations:~:text=Also%2C%20beware%20of%20writing%20if%20x%20when%20you%20really%20mean%20if%20x%20is%20not%20None)
- [型ヒントからわかる場合は省略しても良いかもしれない](https://github.com/h1rosaka/arai60/pull/5/files/bc2027b5d4f57992bbe8afb6924f3cf95e591cc3#r1716372910)
- if a: だと 0や何らかの空集合にもFalseと反応するから注意しろというのが本質
- Google は大規模に開発しているからここらへん細かいのかも?
- 変わらない悩みなの面白い
- [stack overflow](https://stackoverflow.com/questions/7816363/if-a-vs-if-a-is-not-none)
- [Reddit](https://www.reddit.com/r/learnpython/comments/of5s8i/whats_the_most_pythonic_way_of_checking_if/)
- 今回は 読みやすさ優先で while not_reversedを使います。
## step3
三回連続で書いた
step3最後のものと同じ。

```python
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
reversed = None

Choose a reason for hiding this comment

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

reversedはbuiltin functionにあるので命名としては避けるのが無難かなと思います。
https://docs.python.org/3/library/functions.html#reversed

Copy link
Owner Author

@bumbuboon bumbuboon Oct 24, 2024

Choose a reason for hiding this comment

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

biltinは関数ですが僕が定義したのは変数なので使っても良いのではないかと思って使ってしまいました。
reversed_でしたら大丈夫でしょうか?

Choose a reason for hiding this comment

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

これとか実行してみると良いと思います。

print(type(reversed))
reversed = 5
print(type(reversed))

Choose a reason for hiding this comment

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

あまり詳しくないですが、local name spaceにおいてはreversedも上書きされてしまうっぽいです
https://docs.python.org/3/glossary.html#term-namespace

Copy link

Choose a reason for hiding this comment

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

プログラムが動くか動かないかという意味で大丈夫か大丈夫ではないかというと大丈夫です。keyword はだめです。(keyword 一覧確認しておきましょう。)

ただ、builtin も後々の書き換えでそれを使いたい人が現れると混乱させるので、書き慣れた人は避けるでしょう。(builtin 一覧も確認しておきましょう。)

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほど、ありがとうございます。確認してみたいと思います。

Copy link

Choose a reason for hiding this comment

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

公式ドキュメントを探して下のように貼り付けるまでを、習慣にするといいでしょう。
https://docs.python.org/3/reference/lexical_analysis.html#keywords
https://docs.python.org/3/library/builtins.html#module-builtins

専門家の振る舞いとして、読んで調べるのは大事です。

not_reversed = head

while not_reversed:
next_temp = not_reversed.next
not_reversed.next = reversed

reversed = not_reversed
not_reversed = next_temp

return reversed