Skip to content
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

Add 82. Remove Duplicates from Sorted List II.md #4

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Conversation

if node.next is None or node.val != node.next.val:
tail.next = node
tail = tail.next
node = node.next
Copy link

Choose a reason for hiding this comment

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

切断しておいて繋ぐ、が私の意図とは異なるもので伝わっている気がします。
ここの部分は、node が取り除かれないものであると確定して、node が node.next に移った瞬間だと思いますが、この時点で tail.next が node を指しているはずです。
つまり、「dummy_head からたどれるものは、すべて最終的な完成品に含まれると確定したものだけである」とはなっていませんね。
「切断しておいて繋ぐ」の私の意図は、ここで tail.next = None することによって、「dummy_head からたどれるものは、すべて最終的な完成品に含まれると確定したものだけである」ようにしようということです。そうすると return の前の tail.next = None が不要になります。

Copy link
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。

  • 切断しておいて繋ぐ:nodeが取り除かれないと確定したタイミングでtailを更新(tail.nextはそのままnodeの先に繋がる)
  • 繋いでおいて切断:今注目しているnode(これ自体は、取り除かれないと確定)について、node.nextを更新

みたいな認識でした。
たしかに、このコードだと切断のニュアンスはありませんね。

Comment on lines +144 to +145
node = dummy_head
while node is not None:
Copy link

Choose a reason for hiding this comment

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

nodeをNoneチェックする必要があるか疑問に感じました。
というのも、nodeの初期値がNoneの可能性があるheadなら、nodeにはNoneチェックが必要なものが入っていって、nodeがNoneの可能性がない実体で始まるなら、それと同じようにnodeにはNoneの可能性がない実体があるものが入っていくのかなと個人的には考えます。

繋いでから切断、切断してから繋ぐみたいなのも、自分自身ちゃんとは分かっていないので、もしかしたら変な指摘かもしれません🙇‍♂️

Copy link
Owner Author

Choose a reason for hiding this comment

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

繋いでから切断、切断してから繋ぐみたいなのも、自分自身ちゃんとは分かっていないので、もしかしたら変な指摘かもしれません🙇‍♂️

こちらですが、言い換えただけにはなりますが、自分にとって腑に落ちたイメージを話します。
まず、繋いでから切断する、切断しておいて繋ぐという解法のどちらも以下の共通点があります。

  • 与えられたsingly-linked listをheadから順に見ていく
  • 値に重複があれば、適宜ノードの繋ぐ先を繋ぎかえる
  • 返り値として、「重複のない、昇順に並んだsingly-linked listのhead」を返す

次に相違点ですが、「与えられたsingly-linked listをheadから順に見ていく」ループの中で、保持している値に違いがあります。

  • 切断しておいて繋ぐ:常に、「回答となるものの一部もしくは全部」、つまり、「重複のない、昇順に並んだsingly-linked list」を保持します。
  • 繋いでから切断:「回答となるものの一部もしくは全部 + まだチェックしていないlinked-list」を保持します。

ですので、例えば1 -> 1 -> 2 -> 3 -> 4 -> 4 -> Noneなるリストが入力として与えられた場合、各数値ごとにそれぞれ次のような遷移をします。

  • 切断しておいて繋ぐ
    (何もチェックしていない)
    dummy_head -> None
    1 -> 1 -> 2 -> 3 -> 4 -> 4 -> None
    
    (1までチェックした)
    dummy_head -> None
    2 -> 3 -> 4 -> 4 -> None
    
    (2までチェックした)
    dummy_head -> 2 -> None
    3 -> 4 -> 4 -> None
    
    (3までチェックした)
    dummy_head-> 2 -> 3 -> None
    4 -> 4 -> None
    
    (4までチェックした)
    dummy_head-> 2 -> 3 -> None
    None
    
    (末尾まで到達)
    dummy_head -> 2 -> 3 -> None
    None
    
  • 繋いでおいて切断する
    (何もチェックしていない)
    dummy_head -> 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> None
    
    (1までチェックした)
    dummy_head -> 2 -> 3 -> 4 -> 4 -> None
    
    (2までチェックした)
    dummy_head -> 2 -> 3 -> 4 -> 4 -> None
    
    (3までチェックした)
    dummy_head -> 2 -> 3 -> 4 -> 4 -> None
    
    (4までチェックした)
    dummy_head -> 2 -> 3 -> None
    
    (末尾まで到達)
    dummy_head -> 2 -> 3 -> None
    

どちらの解法も考えやすい部分と、考えにくい部分があると思います。
切断しておいて繋ぐ場合、ループ内で意味的に変わらないものが返り値の条件を満たしている点では考えやすいですが、一つのリストの繋ぎ換えをしているのに、元のリストと返り値となるリストを別で扱うようなイメージになる点が少し考えにくいです(これは、ノードの繋ぎ変えの順序を間違えるとうまく動作しない面倒さに現れていると思います)。
一方で、繋いでおいて切断するものは、一つのリストを繋ぎかえているイメージはつきやすい一方、返り値の条件を満たすものが最後の最後まで出てこない面倒さがあると思います。また、単にチェック済みのノードの末尾をtailと置くと、tail.next以降にもリストがつながっていることがあり、下手な命名をすると単語の意味が実際と結びつきません。
どちらの解法もstep 4で追加したので、よければ見てみてください(step 3までの解法は、すべて繋いでおいて切断するになっています。認識違いでした)。

nodeをNoneチェックする必要があるか疑問に感じました。
というのも、nodeの初期値がNoneの可能性があるheadなら、nodeにはNoneチェックが必要なものが入っていって、nodeがNoneの可能性がない実体で始まるなら、それと同じようにnodeにはNoneの可能性がない実体があるものが入っていくのかなと個人的には考えます。

こちらはおそらく型に誤解があるのではないかと感じました。nodeの初期値がdummy_head(=ListNode(next=head))となっているため、nodeの型をListNodeと捉えているのではないでしょうか?
僕の想定では、この型はOptional[ListNode]( = ListNode | None)となっています。というのも、nodeを末尾まで探索していくと、サイクルがなければいずれはnode = Noneとなるからです。こちらについては、typeアノテーションしておくべきだったかもしれません。

こちらが参考になれば幸いです。コメントありがとうございます。

Copy link

Choose a reason for hiding this comment

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

繋いでから切断、切断してから繋ぐ

これ、私が適当に様子を表現しただけで、特に専門用語ではありませんので、それほどこれ自体を真面目に考える必要はないです。

ただ、これ、ループごとに仕事の引き継ぎをしていると思う(物理的にこれを繋ぎ変える仕事をしていると考える、下のリンク参照)と、「こういう状態にして引き継ぐからね」という約束をどうするかはとても大切なはずです。

ichika0615/arai60#3 (comment)
列車
https://discord.com/channels/1084280443945353267/1195700948786491403/1197102971977211966

https://discord.com/channels/1084280443945353267/1231966485610758196/1239417493211320382

Copy link

Choose a reason for hiding this comment

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

丁寧な説明ありがとうございます!繋いでから切断、切断してから繋ぐのイメージがわかりました

こちらはおそらく型に誤解があるのではないかと感じました。nodeの初期値がdummy_head(=ListNode(next=head))となっているため、nodeの型をListNodeと捉えているのではないでしょうか?

自分の書き方が悪くて意図を伝えられてませんでした。意図としてはnodeの初期値がListNodeなら、ループが進んでも同様に、nodeをOptionalの付かないListNodeの型にしておける書き方がある、ということを伝えたかったです。自分が書いた1重whileのコードと動作が同じなのに、こちらだと2重whileのコードになっていたので、nodeのNoneチェックするwhileを減らせると思いコメントしました。

```python3
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
def hasSameValueAsNextNode(node):
Copy link

Choose a reason for hiding this comment

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

LeetCode で指定されている関数名は lowerCamel なのですが、 Python では一般的には関数名は lower_snake で書くことが多いと思います。lower_camel で書くことをお勧めいたします。

https://peps.python.org/pep-0008/#function-and-variable-names

Function names should be lowercase, with words separated by underscores as necessary to improve readability.

https://google.github.io/styleguide/pyguide.html#316-naming

function_name

while node.next is not None and node.val == node.next.val:
node = node.next
if isSameAsNext(node.next):
return skipDuplicates(node.next)
Copy link

Choose a reason for hiding this comment

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

同じ値を持つ一連のノードを取り除いたあと、その直後の同じ値を持つ一連のノードを取り除くために再起関数を使うという点が、不必要に複雑に感じました。

dummy_head = ListNode(next=head)
node = dummy_head
while node is not None:
has_duplicate = False
Copy link

Choose a reason for hiding this comment

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

has_duplicate フラグで処理を切り替える部分が不必要に複雑に感じました。 step3 の最初のソースコードのほうがシンプルだと感じました。

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.

4 participants