Skip to content

Commit e2b6419

Browse files
authored
Merge pull request #1335 from seungriyou/main
[seungriyou] Week 04 Solutions
2 parents 80d8847 + 00739f9 commit e2b6419

File tree

5 files changed

+291
-0
lines changed

5 files changed

+291
-0
lines changed

coin-change/seungriyou.py

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# https://leetcode.com/problems/coin-change/
2+
3+
from functools import cache
4+
from typing import List
5+
import math
6+
7+
class Solution:
8+
def coinChange_n(self, coins: List[int], amount: int) -> int:
9+
"""
10+
[Complexity]
11+
- TC: O(n * amount)
12+
- SC: O(n * amount)
13+
14+
[Approach]
15+
각 coin을 무한히 많이 사용할 수 있으므로 unbounded knapsack problem 이다.
16+
이때, 가치를 최대화하는 것 == 동전의 개수를 최소화 하는 것이다.
17+
따라서 2D DP로 풀 수 있다.
18+
"""
19+
20+
INF = amount + 1
21+
n = len(coins)
22+
23+
# dp[i][j] = i번째 coin까지 사용했을 때, j 만큼의 amount를 만들 수 있는 coin의 최소 개수
24+
dp = [[INF] * (amount + 1) for _ in range(n + 1)]
25+
dp[0][0] = 0
26+
27+
for i in range(1, n + 1): # -- coin
28+
dp[i][0] = 0
29+
for j in range(1, amount + 1): # -- amount
30+
if j < coins[i - 1]:
31+
dp[i][j] = dp[i - 1][j] # 현재 coin을 넣을 수 없음
32+
else:
33+
dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1) # min(현재 coin을 넣지 X 경우, 현재 coin을 넣는 경우)
34+
35+
return dp[n][amount] if dp[n][amount] != INF else -1
36+
37+
def coinChange_1(self, coins: List[int], amount: int) -> int:
38+
"""
39+
[Complexity]
40+
- TC: O(n * amount)
41+
- SC: O(amount)
42+
43+
[Approach]
44+
매 단계에서 다음의 두 값만 확인하므로, 2D DP를 rolling array 방식으로 1D DP로 space optimize 할 수 있다.
45+
- dp[i - 1][j]
46+
- dp[i][j - coins[i - 1]]
47+
"""
48+
49+
INF = amount + 1
50+
51+
dp = [INF] * (amount + 1)
52+
dp[0] = 0
53+
54+
for coin in coins:
55+
for amnt in range(coin, amount + 1):
56+
dp[amnt] = min(dp[amnt], dp[amnt - coin] + 1) # min(현재 coin을 넣지 X 경우, 현재 coin을 넣는 경우)
57+
58+
return dp[amount] if dp[amount] != INF else -1
59+
60+
def coinChange_b(self, coins: List[int], amount: int) -> int:
61+
"""
62+
[Complexity]
63+
- TC: O(n * amount) (금액 1 ~ amount 각각에 대해 len(coins) 만큼 확인)
64+
- SC: O(amount) (seen & q)
65+
66+
[Approach]
67+
BFS로 최단거리를 찾듯이 접근해도 된다. 이때의 최단거리란 최소 개수를 의미한다.
68+
"""
69+
from collections import deque
70+
71+
q = deque([(0, 0)]) # (총 금액, coin 개수)
72+
seen = {0} # 이미 확인한 총 금액
73+
74+
while q:
75+
amnt, n = q.popleft()
76+
77+
# base condition
78+
if amnt == amount:
79+
return n
80+
81+
# iter
82+
for coin in coins:
83+
if (new_amnt := amnt + coin) <= amount and new_amnt not in seen:
84+
q.append((new_amnt, n + 1))
85+
seen.add(new_amnt)
86+
87+
return -1
88+
89+
def coinChange(self, coins: List[int], amount: int) -> int:
90+
"""
91+
[Complexity]
92+
- TC: O(n * amount) (금액 0 ~ amount, 각각 len(coins) 만큼 확인)
93+
- SC: O(amount) (@cache 저장 공간, call stack)
94+
95+
[Approach]
96+
bottom-up이었던 DP 뿐만 아니라, 더 직관적인 top-down 접근도 가능하다.
97+
이때 @cache를 사용하면 memoization을 통해 더 최적화할 수 있다.
98+
"""
99+
100+
@cache
101+
def dp(amnt):
102+
# base condition
103+
if amnt == 0:
104+
return 0
105+
if amnt < 0:
106+
return math.inf
107+
108+
# recur
109+
return min(dp(amnt - coin) + 1 for coin in coins)
110+
111+
res = dp(amount)
112+
113+
return res if res != math.inf else -1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def findMin(self, nums: List[int]) -> int:
7+
"""
8+
[Complexity]
9+
- TC: O(logn)
10+
- SC: O(1)
11+
12+
[Approach]
13+
기본적으로 rotated sorted array에서 O(logn) time에 minimum element를 찾아야하므로
14+
binary search를 사용한다. 규칙을 찾아보면 다음과 같다.
15+
- nums[mid] > nums[hi]: min element가 오른쪽 영역인 (mid, hi]에 있음
16+
- nums[mid] < nums[hi]: min element가 왼쪽 영역인 [lo, mid]에 있음
17+
"""
18+
19+
lo, hi = 0, len(nums) - 1
20+
21+
while lo < hi:
22+
mid = lo + (hi - lo) // 2
23+
if nums[mid] > nums[hi]:
24+
lo = mid + 1 # (mid, hi]에 min element 존재
25+
else:
26+
hi = mid # [lo, mid]에 min element 존재
27+
28+
return nums[lo]
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# https://leetcode.com/problems/maximum-depth-of-binary-tree/
2+
3+
from typing import Optional
4+
5+
# Definition for a binary tree node.
6+
class TreeNode:
7+
def __init__(self, val=0, left=None, right=None):
8+
self.val = val
9+
self.left = left
10+
self.right = right
11+
12+
class Solution:
13+
def maxDepth_recur(self, root: Optional[TreeNode]) -> int:
14+
"""
15+
[Complexity]
16+
- TC: O(n) (모든 node를 한 번씩 방문)
17+
- SC: O(h) (call stack) (h = logn ~ n)
18+
19+
[Approach] recursive
20+
재귀적으로 max(left subtree의 depth, right subtree의 depth) + 1 을 구하면 된다.
21+
base condition(= 현재 노드가 None인 경우)에서는 0을 반환한다.
22+
"""
23+
24+
def get_max_depth(node):
25+
# base condition
26+
if not node:
27+
return 0
28+
29+
# recur
30+
return max(get_max_depth(node.right), get_max_depth(node.left)) + 1
31+
32+
return get_max_depth(root)
33+
34+
def maxDepth(self, root: Optional[TreeNode]) -> int:
35+
"""
36+
[Complexity]
37+
- TC: O(n) (모든 node를 한 번씩 방문)
38+
- SC: O(w) (트리의 너비) (w = 1 ~ n / 2)
39+
40+
[Approach] iterative
41+
BFS 처럼 이진 트리를 레벨 별로 순회하며 depth를 1씩 증가시킬 수 있다.
42+
"""
43+
depth = 0
44+
curr_level = [root] if root else []
45+
46+
while curr_level:
47+
next_level = []
48+
for node in curr_level:
49+
if node.left:
50+
next_level.append(node.left)
51+
if node.right:
52+
next_level.append(node.right)
53+
54+
curr_level = next_level
55+
depth += 1
56+
57+
return depth

merge-two-sorted-lists/seungriyou.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# https://leetcode.com/problems/merge-two-sorted-lists/
2+
3+
from typing import Optional
4+
5+
# Definition for singly-linked list.
6+
class ListNode:
7+
def __init__(self, val=0, next=None):
8+
self.val = val
9+
self.next = next
10+
11+
class Solution:
12+
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
13+
"""
14+
[Complexity]
15+
- TC: O(n)
16+
- SC: O(1)
17+
18+
[Approach]
19+
이미 list1, list2가 non-decreasing order로 정렬되어 있으므로, 각 linked list를 하나씩 비교하며 더 작은 값을 가진 노드부터 모으면 된다.
20+
그리고 list1, list2 중에 하나라도 다 본 경우, 남은 linked list를 끝에 붙이면 된다.
21+
"""
22+
res = curr = ListNode()
23+
24+
while list1 and list2:
25+
if list1.val <= list2.val:
26+
curr.next = list1
27+
list1 = list1.next
28+
else:
29+
curr.next = list2
30+
list2 = list2.next
31+
curr = curr.next
32+
33+
if list1:
34+
curr.next = list1
35+
if list2:
36+
curr.next = list2
37+
38+
return res.next

word-search/seungriyou.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# https://leetcode.com/problems/word-search/
2+
3+
from typing import List
4+
5+
class Solution:
6+
def exist(self, board: List[List[str]], word: str) -> bool:
7+
"""
8+
[Complexity]
9+
- TC: O(m * n * 4^L) (L = word의 길이)
10+
- 한 경로에서 최대 L번 재귀 호출
11+
- 각 cell 당 3~4 방향 가능
12+
- SC: O(m * n + L) (visited + call stack)
13+
- visited 배열 대신 board에 inplace로 표시하면 O(L)으로 최적화 가능
14+
15+
[Approach]
16+
주어진 word를 순차적으로 확인하기 위해 backtracking으로 접근할 수 있다. (가지치기 가능)
17+
맨 처음에 word를 구성하는 문자가 board에 모두 존재하는지 확인한다면 run time을 줄일 수 있다.
18+
"""
19+
# early stop (word를 구성하는 문자가 board에 모두 존재하는지 확인)
20+
if set(word) - set(l for row in board for l in row):
21+
return False
22+
23+
m, n = len(board), len(board[0])
24+
visited = [[False] * n for _ in range(m)]
25+
26+
def backtrack(r, c, idx):
27+
# base condition
28+
if idx == len(word):
29+
return True
30+
if (
31+
not (0 <= r < m and 0 <= c < n) # (1) 범위를 벗어나거나
32+
or visited[r][c] # (2) 이미 방문했거나
33+
or board[r][c] != word[idx] # (3) 주어진 word와 다른 경우
34+
):
35+
return False
36+
37+
# backtrack
38+
visited[r][c] = True
39+
if (
40+
backtrack(r - 1, c, idx + 1)
41+
or backtrack(r + 1, c, idx + 1)
42+
or backtrack(r, c - 1, idx + 1)
43+
or backtrack(r, c + 1, idx + 1)
44+
):
45+
return True
46+
visited[r][c] = False
47+
48+
return False
49+
50+
for i in range(m):
51+
for j in range(n):
52+
if backtrack(i, j, 0):
53+
return True
54+
55+
return False

0 commit comments

Comments
 (0)