Skip to content
Open
Show file tree
Hide file tree
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
46 changes: 46 additions & 0 deletions 0019.Remove-Nth-Node-From-End-of-List/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 19. Remove Nth Node From End of List

## step1
一巡して長さを求めて、もう一巡して削除を行う。8mぐらい。今回はミスがなかった。

削除したNodeのnextをNoneに変更した。メモリリークを明示的に防ぐためである。

制約も小さく、解くだけなら簡単な問題なので、他の方針も考える。

> Follow up: Could you do this in one pass?

hashmapを使えば実現できる

nth がendから数えたものなので、一度最後まで走査しないと削除が実行できない。これ以外の方針は思いつかない。

## 他の人のコード

https://github.com/thonda28/leetcode/pull/18

slowとfastを使うのか、なるほど。自分で思いつきたいところだった。

> step1のslow, fastの方が(個人的には)読みやすく感じました


このコードでは上述のメモリリークの対策はしていないようだが、した方が良いように思う

追記: このコードがなくても参照カウントが0になるためメモリリークは起きないと思われる

## step2

## pythonのメモリ管理について

https://daobook.github.io/devguide/garbage_collector.html

> The main garbage collection algorithm used by CPython is reference counting.

> ... it would never be cleaned just by simple reference counting. For this reason some additional machinery is needed to clean these reference cycles between objects once they become unreachable. This is the cyclic garbage collector, usually called just Garbage Collector (GC), ...

> Doubly linked lists are used because they efficiently support most frequently required operations. In general, the collection of all objects tracked by GC are partitioned into disjoint sets, each in its own doubly linked list.

- 主に参照カウント方式でgcが実装されている
- それだけでは防げない循環参照をcyclic garbage collectorで処理する
- 双方向リストによってオブジェクトの集合ごとに管理されている
- 循環参照の特定: 双方向連結リストにまとめる -> 身内同士の参照カウントをdecrement -> 参照が正のオブジェクトから辿れるオブジェクトをBFSし、参照カウント0のものは1にする -> 参照カウント0のオブジェクトを削除
- 再帰関数を使わない -> 省メモリ
- 世代別GC: 「新しく作られたオブジェクトのほとんどは、すぐに使われなくなる」仮説を利用する。世代の小さいものを優先的に削除する
25 changes: 25 additions & 0 deletions 0019.Remove-Nth-Node-From-End-of-List/step1_one_pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
index_to_node = {}
index = 0
node = head
while node is not None:
index_to_node[index] = node
node = node.next
index += 1

index_from_start = index - n
if index_from_start < 0:
raise ValueError(f"n is too large: {n}")
if index_from_start == 0:
return head.next

index_to_node[index_from_start - 1].next = index_to_node[index_from_start].next
index_to_node[index_from_start].next = None

return head
28 changes: 28 additions & 0 deletions 0019.Remove-Nth-Node-From-End-of-List/step1_two_pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
count = 0
node = head
while node is not None:
node = node.next
count += 1

index_from_start = count - n
if index_from_start < 0:
raise ValueError(f"n is too large: {n}")
if index_from_start == 0:
return head.next

node = head
for _ in range(index_from_start - 1):
node = node.next

temp = node.next.next
node.next.next = None
node.next = temp

return head
27 changes: 27 additions & 0 deletions 0019.Remove-Nth-Node-From-End-of-List/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next


class Solution:
def removeNthFromEnd(self, head: ListNode | None, n: int) -> ListNode | None:
dummy = ListNode(next=head)
slow = dummy
fast = dummy
Comment on lines +11 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

スピード自体は同じなので、behind、aheadなど位置関係を表す単語を使っても良さそうです。

for _ in range(n):
fast = fast.next

if fast is None:
raise ValueError(f"n is too large: {n}")

while fast.next:
slow = slow.next
fast = fast.next

removed_node = slow.next
slow.next = removed_node.next
removed_node.next = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

このコードでは上述のメモリリークの対策はしていないようだが、した方が良いように思う

上の話に相当する部分だと思いますが、removed_node自体へのレファレンスカウントが0になり削除されそうなので、Noneにセットしないでも、特に悪影響はなさそうですかね。

Pythonでメモリリークが起きるのはどういう時ですかね?

@tom4649 tom4649 Jun 26, 2026

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.

この部分はなくてもメモリリークは起こらなそうです。参照カウント方式のGCが行われるためですね。
ついでに以下をざっとみました。
https://daobook.github.io/devguide/garbage_collector.html

グローバル変数などプログラム終了時まで解放されないオブジェクトが膨らむとメモリリークが起きそうです(AIに聞きました)

Pythonでメモリリークが起きるのはどういう時ですかね?

調べてみるとpandasなどc言語で書かれたライブラリの内部でメモリリークが起きる場合もあるようです
https://zendesk.engineering/hunting-for-memory-leaks-in-python-applications-6824d0518774

Some Python libraries could potentially have memory leaks. E.g. pandas have quite a few known memory leaks issues.


return dummy.next