Skip to content

Commit

Permalink
add solution: course-schedule
Browse files Browse the repository at this point in the history
  • Loading branch information
dusunax committed Feb 15, 2025
1 parent a78e183 commit 869e90b
Showing 1 changed file with 184 additions and 0 deletions.
184 changes: 184 additions & 0 deletions course-schedule/dusunax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
'''
# 207
์ฐธ๊ณ  ์˜์ƒ: https://www.youtube.com/watch?v=EgI5nU9etnU
๋ฌธ์ œ ํ’€์ด: https://www.algodale.com/problems/course-schedule/
## ๋ฌธ์ œ ์ •๋ฆฌ
๐Ÿ‘‰ prerequisites๋ž€? ํ•„์ˆ˜ ์„ ์ˆ˜ ๊ณผ๋ชฉ์ด๋‹ค.
๐Ÿ‘‰ ๋ฐฉํ–ฅ์„ฑ์ด ์žˆ๋Š” ์—ฐ๊ฒฐ ๊ด€๊ณ„์ด๋ฏ€๋กœ, Directed Graph๋‹ค.
๐Ÿ‘‰ Cycle ๋ฐœ์ƒ ์‹œ, ์ฝ”์Šค๋ฅผ ์ด์ˆ˜ํ•  ์ˆ˜ ์—†๋‹ค.(์„œ๋กœ ์˜์กดํ•˜๋Š” ์ˆœํ™˜์ด ์žˆ์–ด์„œ ๋์—†์ด ๋Œ๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ)
## ํ•ด๊ฒฐ ๋ฐฉ์‹ ๋‘๊ฐ€์ง€
1. BFS, Queue, Topological Sort: ์œ„์ƒ ์ •๋ ฌ
2. DFS, Cycle Detection: ์ˆœํ™˜ ํƒ์ง€
### ์œ„์ƒ ์ •๋ ฌ(Topological Sort) - BFS, Queue
- ์ง„์ž… ์ฐจ์ˆ˜(indegree): ๋…ธ๋“œ๋กœ ๋“ค์–ด์˜ค๋Š” ํ™”์‚ดํ‘œ ์ˆ˜
- ๊ทธ๋ž˜ํ”„๋กœ ์ธ์ ‘ ๋ฆฌ์ŠคํŠธ ๊ตฌ์„ฑ
- Queue์— ๋„ฃ๊ธฐ
- Queue BFS ํƒ์ƒ‰
- ๋ชจ๋“  ๊ณผ๋ชฉ์„ ๋“ค์—ˆ๋Š”์ง€ ํ™•์ธ
### ์ˆœํ™˜ ํƒ์ง€(Cycle Detection) - DFS
- ๊ทธ๋ž˜ํ”„๋กœ ์ธ์ ‘ ๋ฆฌ์ŠคํŠธ ๊ตฌ์„ฑ
- ๋ฐฉ๋ฌธ ์ƒํƒœ ๋ฐฐ์—ด ์ดˆ๊ธฐํ™”
- dfs ํ•จ์ˆ˜
- ๋ชจ๋“  ๋…ธ๋“œ์— ๋Œ€ํ•ด dfs ์‹คํ–‰
## TC & SC
- ์‹œ๊ฐ„ ๋ณต์žก๋„์™€ ๊ณต๊ฐ„ ๋ณต์žก๋„๋Š” O(V + E)๋กœ ๋™์ผํ•˜๋‹ค.
```
V: ๋…ธ๋“œ ์ˆ˜(๊ณผ๋ชฉ ์ˆ˜)
E: ๊ฐ„์„  ์ˆ˜(์„ ์ˆ˜ ๊ณผ๋ชฉ ๊ด€๊ณ„ ์ˆ˜)
```
### TC is O(V + E)
๋‘ ๋ฐฉ๋ฒ• ๋ชจ๋‘, ๊ทธ๋ž˜ํ”„์˜ ๋ชจ๋“  ๋…ธ๋“œ์™€ ๊ฐ„์„ ์„ ํ•œ ๋ฒˆ์”ฉ ํ™•์ธํ•จ
- BFS: ๋ชจ๋“  V๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ, ๊ฐ ๋…ธ๋“œ์—์„œ ๋‚˜๊ฐ€๋Š” E๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ์ฐจ์ˆ˜๋ฅผ ์ค„์ž„
- DFS: ๋ชจ๋“  V๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ, ๊ฐ ๋…ธ๋“œ์—์„œ ์—ฐ๊ฒฐ๋œ E๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ๊นŠ์ด ํƒ์ƒ‰
### SC is O(V + E)
- O(V+E): V + E๋ฅผ ์ €์žฅํ•˜๋Š” ์ธ์ ‘ ๋ฆฌ์ŠคํŠธ ๊ทธ๋ž˜ํ”„
- O(V)'s: ๋ฐฉ๋ฌธ ์ƒํƒœ ์ €์žฅ, ์ง„์ž… ์ฐจ์ˆ˜ ๋ฐฐ์—ด, BFS ํ, DFS ํ˜ธ์ถœ ์Šคํƒ
## ์œ„์ƒ์ •๋ ฌ(BFS) vs ์ˆœํ™˜ํƒ์ง€(DFS)๐Ÿค”
### BFS๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ
- ๋ฐ˜๋ณต๋ฌธ์„ ์‚ฌ์šฉํ•œ BFS๊ฐ€ indegree(์ง„์ž…์ฐจ์ˆ˜) ๊ฐœ๋…์ด ๋ณด๋‹ค ์ง๊ด€์ ์ด๋ฏ€๋กœ => "์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธ"ํ•  ๋•Œ ๋ช…ํ™•ํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ง„์ž… ์ฐจ์ˆ˜๊ฐ€ 0์ธ ๋…ธ๋“œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ฒ˜๋ฆฌ
- ์„ ์ˆ˜ ๊ณผ๋ชฉ์„ ๋‹ค ๋“ค์€ ๊ณผ๋ชฉ์€ ์ง„์ž… ์ฐจ์ˆ˜๊ฐ€ 0์ด ๋˜๋ฏ€๋กœ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ณผ๋ชฉ์ด๋ผ๋Š” ์ ์ด ๋ช…ํ™•ํ•จ
```
ํ‚ค์›Œ๋“œ: ์ฒ˜๋ฆฌ ์ˆœ์„œ๋ฅผ ์ถœ๋ ฅ, ์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€
```
### DFS๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ
- DFS ์ˆœํ™˜ ํƒ์ง€๋Š” "์ˆœํ™˜ ์—ฌ๋ถ€"๊ฐ€ ํ•ต์‹ฌ์ผ ๋•Œ ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.
- ์ƒํƒœ(Status)๋ฅผ ์‚ฌ์šฉํ•ด์„œ, ๋ฐฉ๋ฌธ ์ค‘์ธ ๋…ธ๋“œ ์ƒํƒœ๋ฅผ ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•œ๋‹ค๋ฉด ์ˆœํ™˜์ด ์žˆ์Œ์„ ๋ฐ”๋กœ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
- ์ˆœํ™˜์ด ๋ฐœ๊ฒฌ๋˜๋ฉด ๋ฐ”๋กœ ์ค‘๋‹จํ•˜๋ฏ€๋กœ, ์ˆœํ™˜ ํƒ์ง€์— ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.
```
ํ‚ค์›Œ๋“œ: ์‚ฌ์ดํด์ด ์žˆ๋Š”์ง€ ํŒ๋‹จ
```
### +a) `@cache`๋ฅผ ํ™œ์šฉํ•ด๋ณด์ž.
- ํŒŒ์ด์„  3.9~ ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ํ•จ์ˆ˜
- ์ˆœ์ˆ˜ ํ•จ์ˆ˜ + ์žฌ๊ท€ ์ตœ์ ํ™”์— ์‚ฌ์šฉ (์™ธ๋ถ€ ์˜์กด์„ฑ, ๋ถ€์ˆ˜ํšจ๊ณผ์— ์ฃผ์˜ํ•  ๊ฒƒ)
'''
from enum import Enum

class Status(Enum): # use it to dfs
INITIAL = 1
IN_PROGRESS = 2
FINISHED = 3

class Solution:
'''
1. BFS
์œ„์ƒ ์ •๋ ฌ
'''
def canFinishTopologicalSort(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
indegree = [0] * numCourses
graph = defaultdict(list)

for dest, src in prerequisites:
graph[src].append(dest)
indegree[dest] += 1

queue = deque([i for i in range(numCourses) if indegree[i] == 0])
processed_count = 0

while queue:
node = queue.popleft()
processed_count += 1
for neighbor in graph[node]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
queue.append(neighbor)

return processed_count == numCourses

'''
2. DFS
์ˆœํ™˜ ํƒ์ง€
'''
def canFinishCycleDetection(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
graph = defaultdict(list)

for dest, src in prerequisites:
graph[src].append(dest)

statuses = {i: Status.INITIAL for i in range(numCourses)}

def dfs(node):
if statuses[node] == Status.IN_PROGRESS:
return False
if statuses[node] == Status.FINISHED:
return True

statuses[node] = Status.IN_PROGRESS
for neighbor in graph[node]:
if not dfs(neighbor):
return False
statuses[node] = Status.FINISHED
return True

return all(dfs(crs) for crs in range(numCourses))

'''
3. @cache
ํŒŒ์ด์ฌ 3.9 ์ด์ƒ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”๋ชจ์ด์ œ์ด์…˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ
- ๋™์ผ ์ž…๋ ฅ -> ๋™์ผ ์ถœ๋ ฅ์„ ๋ณด์žฅํ•œ๋‹ค.
- 128๊ฐœ ๊นŒ์ง€๋งŒ ์ €์žฅํ•˜๋Š” @lru_cache๋„ ์žˆ๋‹ค.
'''
def canFinishWithCache(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
graph = defaultdict(list)

for dest, src in prerequisites:
graph[src].append(dest)

traversing = set()

@cache
def dfs(node):
if node in traversing:
return False

traversing.add(node)
result = all(dfs(pre) for pre in graph[node])
traversing.remove(node)
return result

return all(dfs(node) for node in range(numCourses))

'''
4. visited๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ
@cache ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ๋ฉ”๋ชจ์ด์ œ์ด์…˜, ๊ฐ™์€ ์ž…๋ ฅ๊ฐ’์— ๋”ฐ๋ผ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฒŒํ•จ
๊ฒฐ๊ณผ๊ฐ€ ๋ณ€ํ•˜์ง€ ์•Š์„ ๋•Œ ์œ ์šฉํ•จ => dfs(node)๋Š” ์™ธ๋ถ€ ์ƒํƒœ ์ˆœํ™˜ traversing์— ์˜์กดํ•ด์„œ ๋™์ž‘์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค.
๋”ฐ๋ผ์„œ visited set์ด ๋” ์ž์—ฐ์Šค๋Ÿฌ์šธ ์ˆ˜ ์žˆ๋‹ค
'''
def canFinishWithVisited(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
graph = defaultdict(list)

for dest, src in prerequisites:
graph[src].append(dest)

traversing = set()
visited = set()

def dfs(node):
if node in traversing:
return False
if node in visited:
return True

traversing.add(node)
for pre in graph[node]:
if not dfs(pre):
return False
traversing.remove(node)

visited.add(node)
return True

return all(dfs(i) for i in range(numCourses))

0 comments on commit 869e90b

Please sign in to comment.