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
35 changes: 35 additions & 0 deletions 0207.Course-Schedule/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 207. Course Schedule

## step1

DAG判定だと思う。トポロジカルソート。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

閉路検出という言い方のほうが一般的かもしれません。


step1_loop.pyを10mで書いたが遅い。おそらく set の再構築に時間がかかっているのだろう。

step1_backtrack.pyにすると多少速くなった。

ただ空間計算量が O(V^2)

## step2

### 他の人のコード

https://github.com/huyfififi/coding-challenges/pull/34

> NOT_VISITED_YET = 0
> VISITING = 1
> VISITED = 2

これを使って書き直すと、loopの方で速度がBeats 100%となった。

> トポロジカルソートの中でも特にKahn's algorithmと呼ばれるもの

https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%9D%E3%83%AD%E3%82%B8%E3%82%AB%E3%83%AB%E3%82%BD%E3%83%BC%E3%83%88

これを実装。個人的にはqueueを使わないもののほうがわかりやすい。

https://github.com/potrue/leetcode/pull/76

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

https://github.com/thonda28/leetcode/pull/3
27 changes: 27 additions & 0 deletions 0207.Course-Schedule/step1_backtrack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Solution:
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
adjacent_list = [[] for _ in range(numCourses)]
for a, b in prerequisites:
adjacent_list[b].append(a)

checked: set[int] = set()

def no_cycle(node, seen):
if node in seen:
return False
if node in checked:
return True

seen.add(node)
for child in adjacent_list[node]:
if not no_cycle(child, seen):
return False
seen.remove(node)
checked.add(node)
return True

for root in range(numCourses):
if not no_cycle(root, set()):
Copy link
Copy Markdown

@nodchip nodchip May 22, 2026

Choose a reason for hiding this comment

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

二重否定は読み手に取ってやや認知負荷が高くなると思います。 inner function を has_cycle() とし、

if has_cycle(root, set()):

としたほうが読み手にとって優しいと思います。

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.

二重否定が認知負荷を上げるというのは覚えておきます。step2_recursion.pyを書き換えました。

return False

return True
24 changes: 24 additions & 0 deletions 0207.Course-Schedule/step1_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution:
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
adjacent_list = [[] for _ in range(numCourses)]
for a, b in prerequisites:
adjacent_list[b].append(a)

checked: set[int] = set()
for root in range(numCourses):
if root in checked:
continue

stack = [(root, set())]

while stack:
node, seen = stack.pop()
if node in seen:
return False
if node in checked:
continue
checked.add(node)
for child in adjacent_list[node]:
stack.append((child, seen | {node}))

return True
20 changes: 20 additions & 0 deletions 0207.Course-Schedule/step2_kahn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Solution:
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
adjacent_list = [[] for _ in range(numCourses)]
order = [0] * numCourses
for a, b in prerequisites:
adjacent_list[b].append(a)
order[a] += 1

nodes_with_no_parent = [node for node in range(numCourses) if order[node] == 0]

while nodes_with_no_parent:
next_nodes_with_no_parent = []
for node in nodes_with_no_parent:
for child in adjacent_list[node]:
order[child] -= 1
if order[child] == 0:
next_nodes_with_no_parent.append(child)
nodes_with_no_parent = next_nodes_with_no_parent

return sum(order) == 0
25 changes: 25 additions & 0 deletions 0207.Course-Schedule/step2_kahn_que.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from collections import deque


class Solution:
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
adjacent_list = [[] for _ in range(numCourses)]
indegree = [0] * numCourses

for a, b in prerequisites:
adjacent_list[b].append(a)
indegree[a] += 1

frontier = deque(node for node in range(numCourses) if indegree[node] == 0)
visited_count = 0

while frontier:
node = frontier.popleft()
visited_count += 1

for child in adjacent_list[node]:
indegree[child] -= 1
if indegree[child] == 0:
frontier.append(child)

return visited_count == numCourses
36 changes: 36 additions & 0 deletions 0207.Course-Schedule/step2_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
NOT_VISITED = 0
VISITING = 1
VISITED = 2


class Solution:
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
adjacent_list = [[] for _ in range(numCourses)]
for a, b in prerequisites:
adjacent_list[b].append(a)

status = [0] * numCourses
for root in range(numCourses):
if status[root] == VISITED:
continue

stack = [(root, False)]

while stack:
node, is_visited = stack.pop()

if is_visited:
status[node] = VISITED
continue

if status[node] == VISITING:
return False
if status[node] == VISITED:
continue

status[node] = VISITING
stack.append((node, True))
for child in adjacent_list[node]:
stack.append((child, False))

return True
34 changes: 34 additions & 0 deletions 0207.Course-Schedule/step2_recursion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
NOT_VISITED = 0
VISITING = 1
VISITED = 2


class Solution:
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
adjacent_list = [[] for _ in range(numCourses)]
for a, b in prerequisites:
adjacent_list[b].append(a)

status = [NOT_VISITED] * numCourses

def no_cycle(node):
if status[node] == VISITING:
return False

if status[node] == VISITED:
return True

status[node] = VISITING

for child in adjacent_list[node]:
if not no_cycle(child):
return False

status[node] = VISITED
return True

for root in range(numCourses):
if not no_cycle(root):
return False

return True
34 changes: 34 additions & 0 deletions 0207.Course-Schedule/step2_recursion_revised.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
NOT_VISITED = 0
VISITING = 1
VISITED = 2


class Solution:
def canFinish(self, numCourses: int, prerequisites: list[list[int]]) -> bool:
adjacent_list = [[] for _ in range(numCourses)]
for a, b in prerequisites:
adjacent_list[b].append(a)

status = [NOT_VISITED] * numCourses

def has_cycle(node):
if status[node] == VISITING:
return True

if status[node] == VISITED:
return False

status[node] = VISITING

for child in adjacent_list[node]:
if has_cycle(child):
return True

status[node] = VISITED
return False

for root in range(numCourses):
if has_cycle(root):
return False

return True