Skip to content

[seungriyou] Week 04 Solutions #1335

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

Merged
merged 5 commits into from
Apr 26, 2025
Merged
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
113 changes: 113 additions & 0 deletions coin-change/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# https://leetcode.com/problems/coin-change/

from functools import cache
from typing import List
import math

class Solution:
def coinChange_n(self, coins: List[int], amount: int) -> int:
"""
[Complexity]
- TC: O(n * amount)
- SC: O(n * amount)

[Approach]
각 coin을 무한히 많이 사용할 수 있으므로 unbounded knapsack problem 이다.
이때, 가치를 최대화하는 것 == 동전의 개수를 최소화 하는 것이다.
따라서 2D DP로 풀 수 있다.
"""

INF = amount + 1
n = len(coins)

# dp[i][j] = i번째 coin까지 사용했을 때, j 만큼의 amount를 만들 수 있는 coin의 최소 개수
dp = [[INF] * (amount + 1) for _ in range(n + 1)]
dp[0][0] = 0

for i in range(1, n + 1): # -- coin
dp[i][0] = 0
for j in range(1, amount + 1): # -- amount
if j < coins[i - 1]:
dp[i][j] = dp[i - 1][j] # 현재 coin을 넣을 수 없음
else:
dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1) # min(현재 coin을 넣지 X 경우, 현재 coin을 넣는 경우)

return dp[n][amount] if dp[n][amount] != INF else -1

def coinChange_1(self, coins: List[int], amount: int) -> int:
"""
[Complexity]
- TC: O(n * amount)
- SC: O(amount)

[Approach]
매 단계에서 다음의 두 값만 확인하므로, 2D DP를 rolling array 방식으로 1D DP로 space optimize 할 수 있다.
- dp[i - 1][j]
- dp[i][j - coins[i - 1]]
"""

INF = amount + 1

dp = [INF] * (amount + 1)
dp[0] = 0

for coin in coins:
for amnt in range(coin, amount + 1):
dp[amnt] = min(dp[amnt], dp[amnt - coin] + 1) # min(현재 coin을 넣지 X 경우, 현재 coin을 넣는 경우)

return dp[amount] if dp[amount] != INF else -1

def coinChange_b(self, coins: List[int], amount: int) -> int:
"""
[Complexity]
- TC: O(n * amount) (금액 1 ~ amount 각각에 대해 len(coins) 만큼 확인)
- SC: O(amount) (seen & q)

[Approach]
BFS로 최단거리를 찾듯이 접근해도 된다. 이때의 최단거리란 최소 개수를 의미한다.
"""
from collections import deque

q = deque([(0, 0)]) # (총 금액, coin 개수)
seen = {0} # 이미 확인한 총 금액

while q:
amnt, n = q.popleft()

# base condition
if amnt == amount:
return n

# iter
for coin in coins:
if (new_amnt := amnt + coin) <= amount and new_amnt not in seen:
q.append((new_amnt, n + 1))
seen.add(new_amnt)

return -1

def coinChange(self, coins: List[int], amount: int) -> int:
"""
[Complexity]
- TC: O(n * amount) (금액 0 ~ amount, 각각 len(coins) 만큼 확인)
- SC: O(amount) (@cache 저장 공간, call stack)

[Approach]
bottom-up이었던 DP 뿐만 아니라, 더 직관적인 top-down 접근도 가능하다.
이때 @cache를 사용하면 memoization을 통해 더 최적화할 수 있다.
"""

@cache
def dp(amnt):
# base condition
if amnt == 0:
return 0
if amnt < 0:
return math.inf

# recur
return min(dp(amnt - coin) + 1 for coin in coins)

res = dp(amount)

return res if res != math.inf else -1
28 changes: 28 additions & 0 deletions find-minimum-in-rotated-sorted-array/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/

from typing import List

class Solution:
def findMin(self, nums: List[int]) -> int:
"""
[Complexity]
- TC: O(logn)
- SC: O(1)

[Approach]
기본적으로 rotated sorted array에서 O(logn) time에 minimum element를 찾아야하므로
binary search를 사용한다. 규칙을 찾아보면 다음과 같다.
- nums[mid] > nums[hi]: min element가 오른쪽 영역인 (mid, hi]에 있음
- nums[mid] < nums[hi]: min element가 왼쪽 영역인 [lo, mid]에 있음
"""

lo, hi = 0, len(nums) - 1

while lo < hi:
mid = lo + (hi - lo) // 2
if nums[mid] > nums[hi]:
lo = mid + 1 # (mid, hi]에 min element 존재
else:
hi = mid # [lo, mid]에 min element 존재

return nums[lo]
57 changes: 57 additions & 0 deletions maximum-depth-of-binary-tree/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# https://leetcode.com/problems/maximum-depth-of-binary-tree/

from typing import Optional

# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right

class Solution:
def maxDepth_recur(self, root: Optional[TreeNode]) -> int:
"""
[Complexity]
- TC: O(n) (모든 node를 한 번씩 방문)
- SC: O(h) (call stack) (h = logn ~ n)

[Approach] recursive
재귀적으로 max(left subtree의 depth, right subtree의 depth) + 1 을 구하면 된다.
base condition(= 현재 노드가 None인 경우)에서는 0을 반환한다.
"""

def get_max_depth(node):
# base condition
if not node:
return 0

# recur
return max(get_max_depth(node.right), get_max_depth(node.left)) + 1

return get_max_depth(root)

def maxDepth(self, root: Optional[TreeNode]) -> int:
"""
[Complexity]
- TC: O(n) (모든 node를 한 번씩 방문)
- SC: O(w) (트리의 너비) (w = 1 ~ n / 2)

[Approach] iterative
BFS 처럼 이진 트리를 레벨 별로 순회하며 depth를 1씩 증가시킬 수 있다.
"""
depth = 0
curr_level = [root] if root else []

while curr_level:
next_level = []
for node in curr_level:
if node.left:
next_level.append(node.left)
if node.right:
next_level.append(node.right)

curr_level = next_level
depth += 1

return depth
38 changes: 38 additions & 0 deletions merge-two-sorted-lists/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# https://leetcode.com/problems/merge-two-sorted-lists/

from typing import Optional

# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next

class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
"""
[Complexity]
- TC: O(n)
- SC: O(1)

[Approach]
이미 list1, list2가 non-decreasing order로 정렬되어 있으므로, 각 linked list를 하나씩 비교하며 더 작은 값을 가진 노드부터 모으면 된다.
그리고 list1, list2 중에 하나라도 다 본 경우, 남은 linked list를 끝에 붙이면 된다.
"""
res = curr = ListNode()

while list1 and list2:
if list1.val <= list2.val:
curr.next = list1
list1 = list1.next
else:
curr.next = list2
list2 = list2.next
curr = curr.next

if list1:
curr.next = list1
if list2:
curr.next = list2

return res.next
55 changes: 55 additions & 0 deletions word-search/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# https://leetcode.com/problems/word-search/

from typing import List

class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
"""
[Complexity]
- TC: O(m * n * 4^L) (L = word의 길이)
- 한 경로에서 최대 L번 재귀 호출
- 각 cell 당 3~4 방향 가능
- SC: O(m * n + L) (visited + call stack)
- visited 배열 대신 board에 inplace로 표시하면 O(L)으로 최적화 가능

[Approach]
주어진 word를 순차적으로 확인하기 위해 backtracking으로 접근할 수 있다. (가지치기 가능)
맨 처음에 word를 구성하는 문자가 board에 모두 존재하는지 확인한다면 run time을 줄일 수 있다.
"""
# early stop (word를 구성하는 문자가 board에 모두 존재하는지 확인)
if set(word) - set(l for row in board for l in row):
return False

m, n = len(board), len(board[0])
visited = [[False] * n for _ in range(m)]

def backtrack(r, c, idx):
# base condition
if idx == len(word):
return True
if (
not (0 <= r < m and 0 <= c < n) # (1) 범위를 벗어나거나
or visited[r][c] # (2) 이미 방문했거나
or board[r][c] != word[idx] # (3) 주어진 word와 다른 경우
):
return False

# backtrack
visited[r][c] = True
if (
backtrack(r - 1, c, idx + 1)
or backtrack(r + 1, c, idx + 1)
or backtrack(r, c - 1, idx + 1)
or backtrack(r, c + 1, idx + 1)
):
return True
visited[r][c] = False

return False

for i in range(m):
for j in range(n):
if backtrack(i, j, 0):
return True

return False