Skip to content

Commit 5baef7a

Browse files
authored
[okyungjin] WEEK 04 Solutions (#2743)
* 21. Merge Two Sorted Lists * 21. Merge Two Sorted Lists * 104. Maximum Depth of Binary Tree * 153. Find Minimum in Rotated Sorted Array * 79. Word Search * 322. Coin Change * 79. Word Search (공간복잡도 최적화) * 322. Coin Change (반복문 로직 최적화) * 104. Maximum Depth of Binary Tree (변수명 수정)
1 parent 0d88f1d commit 5baef7a

5 files changed

Lines changed: 313 additions & 0 deletions

File tree

coin-change/okyungjin.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# coins: 동전의 가격이 적힌 정수 배열, 각 숫자는 유니크함, 정렬에 대한 언급 X, (1 <= coins.length <= 12)
2+
# amount: 돈의 총합
3+
4+
# amount를 만들 수 있는 가장 적은 동전의 수를 반환한다.
5+
# coins로 amount를 만들 수 없는 경우 -1을 반환
6+
# 동전의 종류는 무한대라고 가정하고 문제를 풀 것
7+
8+
# 처음에는 greedy로 접근하려고 했는데, 금액이 큰 동전을 먼저 선택하는게 최소 동전의 수가 아니라서 dp로 풀이했다.
9+
10+
# [복잡도]
11+
# C: 코인의 개수, # A: amount
12+
# 시간 복잡도: O(A * C)
13+
# 공간 복잡도: O(A)
14+
class Solution:
15+
def coinChange(self, coins: List[int], amount: int) -> int:
16+
NOT_POSSIBLE = amount + 1
17+
18+
# min_coins: 금액을 만드는데 필요한 동전 개수를 저장하는 배열
19+
# min_coins[i]: i원을 만드는데 필요한 최소 동전의 개수
20+
min_coins = [NOT_POSSIBLE] * (amount + 1)
21+
min_coins[0] = 0
22+
23+
# 최적화를 위해 정렬
24+
coins.sort()
25+
26+
for cur_amount in range(1, amount + 1):
27+
# coin: 선택한 동전의 금액
28+
for coin in coins:
29+
# coins가 정렬되어 있으므로, cur_amount 보다 동전의 금액이 더 크면 이후는 확인 불필요
30+
if coin > cur_amount:
31+
break
32+
33+
# 둘 중 더 작은 값으로 업데이트
34+
# 1. 기존에 구한 개수
35+
# 2. 현재 동전을 1개 추가해서 만드는 개수
36+
min_coins[cur_amount] = min(min_coins[cur_amount], min_coins[cur_amount - coin] + 1)
37+
38+
# 초기화된 값 그대로이면 불가능한 조합이므로 -1 반환
39+
if min_coins[amount] == NOT_POSSIBLE:
40+
return -1
41+
# 최소 동전의 개수를 반환
42+
else:
43+
return min_coins[amount]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# [문제 분석]
2+
# nums: 오름차순 정렬, 정수 배열, 중복 없음
3+
# 배열에서 가장 작은 수를 찾아 반환
4+
# 조건: O(log n)으로 작성할 것
5+
6+
# [접근법]
7+
# 정렬된 배열이 주어지고, 배열이 n번 회전한다. 추세가 꺾이는 부분을 찾아 탐색 범위를 좁힌다.
8+
# 배열이 정렬되어 있다는 조건이 있으므로 이진 탐색을 통해 탐색 범위를 좁혀 log n으로 탐색이 가능하다.
9+
10+
# [복잡도]
11+
# 시간 복잡도: O(log n), 이진 탐색을 하므로 log n
12+
# 공간 복잡도: O(1)
13+
class Solution:
14+
def findMin(self, nums: List[int]) -> int:
15+
16+
# 왼쪽/오른쪽 값의 인덱스 계산
17+
left = 0
18+
right = len(nums) - 1
19+
20+
while left < right:
21+
# 중간값 인덱스
22+
mid = (left + right) // 2
23+
24+
# 중간값과 오른쪽 값을 비교해 추세가 꺾이는지 비교
25+
if nums[mid] > nums[right]:
26+
# 추세 안 꺾임, 시작점을 중간값 인덱스 바로 다음으로 옮긴다
27+
left = mid + 1
28+
else:
29+
# 추세 꺾임, 종료지점을 중간값 인덱스로 옮겨준다
30+
right = mid
31+
32+
return nums[left]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Definition for a binary tree node.
2+
# class TreeNode:
3+
# def __init__(self, val=0, left=None, right=None):
4+
# self.val = val
5+
# self.left = left
6+
# self.right = right
7+
8+
# 시간 복잡도: O(N), 모든 노드를 한번 씩 방문
9+
# 공간 복잡도: O(N), 최악의 경우 모든 노드가 큐에 담길 수 있음
10+
class Solution:
11+
def maxDepth(self, root: Optional[TreeNode]) -> int:
12+
if root is None:
13+
return 0
14+
15+
max_depth = 1
16+
stack = [(root, 1)]
17+
18+
while stack:
19+
node, cur_depth = stack.pop()
20+
max_depth = max(cur_depth, max_depth)
21+
22+
if node.left:
23+
stack.append((node.left, cur_depth + 1))
24+
25+
if node.right:
26+
stack.append((node.right, cur_depth + 1))
27+
28+
return max_depth
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# from typing import Optional, List
2+
3+
# class ListNode:
4+
# def __init__(self, val=0, next=None):
5+
# self.val = val
6+
# self.next = next
7+
8+
# # 파이썬의 toString 역할을 하는 __repr__을 [1,3,4] 형태로 출력되게 수정
9+
# def __repr__(self):
10+
# nodes = []
11+
# curr = self
12+
# while curr:
13+
# nodes.append(str(curr.val))
14+
# curr = curr.next
15+
# return "[" + ",".join(nodes) + "]"
16+
17+
# # 파이썬 list를 ListNode 리스트로 변환해주는 헬퍼 함수
18+
# def make_linked_list(arr: List[int]) -> Optional[ListNode]:
19+
# if not arr:
20+
# return None
21+
# dummy = ListNode(0)
22+
# curr = dummy
23+
# for val in arr:
24+
# curr.next = ListNode(val)
25+
# curr = curr.next
26+
# return dummy.next
27+
28+
29+
# list1의 노드 개수를 n, list2의 노드 개수를 m이라 할 때
30+
# 시간 복잡도: O(n + m)
31+
# 공간 복잡도: O(n + m)
32+
class Solution_01:
33+
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
34+
# 맨 앞에 더미 노드를 하나 추가한다
35+
# 없는 상태로 짰는데 if node: else: 구문 늘어나서 맨 앞에 가상의 노드를 하나 추가해줬다
36+
dummy = ListNode(None)
37+
node = dummy
38+
39+
while True:
40+
# list1이 비어있으면 현재 노드 뒤로 list2를 이어준다.
41+
if not list1:
42+
node.next = list2
43+
break
44+
45+
# list2가 비어있으면 현재 노드 뒤로 list1을 이어준다.
46+
if not list2:
47+
node.next = list1
48+
break
49+
50+
# 값을 비교해서 작은 값을 next 노드에 추가한다.
51+
if list1.val <= list2.val:
52+
node.next = ListNode(list1.val)
53+
# list1 하나 소비
54+
list1 = list1.next
55+
else:
56+
node.next = ListNode(list2.val)
57+
# list2 하나 소비
58+
list2 = list2.next
59+
60+
# 다음 노드를 현재 노드로 할당해준다
61+
node = node.next
62+
63+
return dummy.next
64+
65+
66+
# [접근법] Solution_01 의 공간 복잡도를 개선했습니다.
67+
# ListNode를 새로 생성하지 않고 기존 노드를 활용하도록 수정했습니다.
68+
69+
# list1의 노드 개수를 n, list2의 노드 개수를 m이라 할 때
70+
# 시간 복잡도: O(n + m)
71+
# 공간 복잡도: O(1) -> dummy만 사용, 추가 공간 복잡도는 O(1)
72+
class Solution:
73+
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
74+
# 맨 앞에 더미 노드를 하나 추가한다
75+
# 없는 상태로 짰는데 if node: else: 구문 늘어나서 맨 앞에 가상의 노드를 하나 추가해줬다
76+
dummy = ListNode(None)
77+
node = dummy
78+
79+
while True:
80+
if not list1: # list1이 비어있으면 현재 노드 뒤로 list2를 이어준다.
81+
node.next = list2
82+
break
83+
84+
if not list2: # list2가 비어있으면 현재 노드 뒤로 list1을 이어준다.
85+
node.next = list1
86+
break
87+
88+
# 값을 비교해서 작은 값을 next 노드에 추가한다.
89+
if list1.val <= list2.val:
90+
node.next = list1 # 새로운 ListNode를 생성하지 않고 list1를 할당해준다.
91+
list1 = list1.next # list1 하나 소비
92+
else:
93+
node.next = list2 # 새로운 ListNode를 생성하지 않고 list2를 할당해준다.
94+
list2 = list2.next # list2 하나 소비
95+
96+
# 다음 노드를 현재 노드로 할당해준다
97+
node = node.next
98+
99+
return dummy.next
100+
101+
102+
# 4. 예시 입력으로 호출 및 테스트
103+
# if __name__ == "__main__":
104+
# list1 = make_linked_list([1, 2, 4])
105+
# list2 = make_linked_list([1, 3, 4])
106+
107+
# solution = Solution()
108+
# merged_list = solution.mergeTwoLists(list1, list2)
109+
110+
# print(merged_list)

word-search/okyungjin.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# board: 영문 대/소문자 배열, m * n
2+
# board의 상하좌우로 인접한 문자열을 이어붙여서 word를 완성할 수 있는지 여부를 반환한다.
3+
# 단, 셀은 한번만 사용 가능하다
4+
5+
# dfs를 통해 백트랙킹으로 정답을 찾도록 구현했다.
6+
7+
# L: word 의 길이
8+
# 시간 복잡도: O(m * n * 4^L)
9+
# 공간 복잡도: O(m * n + L)
10+
class SolutionA:
11+
def exist(self, board: List[List[str]], word: str) -> bool:
12+
row_size = len(board)
13+
col_size = len(board[0])
14+
15+
# 방문 여부를 기록하는 2차원 배열
16+
visited = [[False] * col_size for _ in range(row_size)]
17+
18+
def dfs(row, col, str_idx) -> bool:
19+
if str_idx == len(word):
20+
return True
21+
22+
# 종료조건1: 보드의 좌표를 넘어가는 경우
23+
if row < 0 or row >= row_size or col < 0 or col >= col_size:
24+
return False
25+
26+
# 종료조건 2: 이미 방문함
27+
# 종료조건 3: 문자열 불일치
28+
if visited[row][col] or board[row][col] != word[str_idx]:
29+
return False
30+
31+
32+
visited[row][col] = True
33+
34+
# 차례대로 상, 하, 좌, 우의 좌표를 탐색
35+
found = (dfs(row - 1, col, str_idx + 1) or \
36+
dfs(row + 1, col, str_idx + 1) or \
37+
dfs(row, col - 1, str_idx + 1) or \
38+
dfs(row, col + 1, str_idx + 1))
39+
40+
visited[row][col] = False # 백트래킹
41+
42+
return found
43+
44+
45+
# 시작점을 찾는다
46+
for r in range(row_size):
47+
for c in range(col_size):
48+
if board[r][c] == word[0] and dfs(r, c, 0):
49+
return True
50+
51+
return False
52+
53+
# SolutionA 의 공간복잡도 최적화 버전, visited 제거
54+
# L: word 의 길이
55+
# 시간 복잡도: O(m * n * 4^L)
56+
# 공간 복잡도: O(L)
57+
class Solution:
58+
def exist(self, board: List[List[str]], word: str) -> bool:
59+
VISITED_MARK = '#'
60+
61+
row_size = len(board)
62+
col_size = len(board[0])
63+
64+
def dfs(row, col, str_idx) -> bool:
65+
if str_idx == len(word):
66+
return True
67+
68+
# 종료조건1: 보드의 좌표를 넘어가는 경우
69+
if row < 0 or row >= row_size or col < 0 or col >= col_size:
70+
return False
71+
72+
# 종료조건 2: 이미 방문함
73+
# 종료조건 3: 문자열 불일치
74+
if board[row][col] == VISITED_MARK or board[row][col] != word[str_idx]:
75+
return False
76+
77+
78+
# VISITED_MARK로 교체하여 방문을 기록
79+
origin_char = board[row][col]
80+
board[row][col] = VISITED_MARK
81+
82+
# 차례대로 상, 하, 좌, 우의 좌표를 탐색
83+
found = (dfs(row - 1, col, str_idx + 1) or \
84+
dfs(row + 1, col, str_idx + 1) or \
85+
dfs(row, col - 1, str_idx + 1) or \
86+
dfs(row, col + 1, str_idx + 1))
87+
88+
# 원본 문자열로 복원
89+
board[row][col] = origin_char
90+
91+
return found
92+
93+
94+
# 시작점을 찾는다
95+
for r in range(row_size):
96+
for c in range(col_size):
97+
if board[r][c] == word[0] and dfs(r, c, 0):
98+
return True
99+
100+
return False

0 commit comments

Comments
 (0)