Skip to content

Commit 7ad1a91

Browse files
authored
Merge pull request #2138 from liza0525/main
[liza0525] WEEK 04 solutions
2 parents f53a921 + 42608a2 commit 7ad1a91

File tree

4 files changed

+213
-0
lines changed

4 files changed

+213
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
class Solution:
2+
def findMin(self, nums: List[int]) -> int:
3+
# 선형 탐색(linear scan) 버전
4+
5+
# 시간 복잡도: O(n)
6+
# - 배열 전체를 뒤에서 앞으로 한 칸씩 순회하면서
7+
# "회전이 끊기는 지점"을 찾는다.
8+
# - 최악의 경우 n-1번 비교.
9+
10+
# 공간 복잡도: O(1)
11+
# - 추가 변수만 사용.
12+
13+
# 배열 길이가 1개면 그 값 자체가 최솟값
14+
min_num = 0
15+
if len(nums) == 1:
16+
return nums[0]
17+
18+
# 뒤에서 앞으로 탐색하면서
19+
# nums[i] < nums[i-1] 지점(회전이 일어난 지점)을 찾는다.
20+
# 해당 지점의 nums[i]가 최솟값
21+
for i in range(len(nums) - 1, -1, -1):
22+
if nums[i] < nums[i - 1]:
23+
min_num = nums[i]
24+
break
25+
26+
return min_num
27+
28+
def findMinBinarySearch(self, nums: List[int]) -> int:
29+
# 이진 탐색(binary search) 버전
30+
31+
# 시간 복잡도: O(log n)
32+
# - 정렬된 배열이 한 번 회전(rotated)된 형태를 이용해
33+
# 절반씩 탐색 범위를 줄인다.
34+
# - mid 기준 왼쪽/오른쪽 어느 쪽이 정렬 상태인지에 따라 탐색 방향 결정
35+
36+
# 공간 복잡도: O(1)
37+
# - 포인터(l, h, mid)만 사용
38+
39+
# l은 1부터 시작하는데,
40+
# mid-1 비교를 안전하게 하기 위함이다.
41+
l, h = 1, len(nums) - 1
42+
43+
while l <= h:
44+
mid = (l + h) // 2
45+
46+
# mid가 최솟값이 되는 전형적인 조건:
47+
# mid 바로 이전 요소가 mid보다 크면 회전 지점
48+
if nums[mid - 1] > nums[mid]:
49+
return nums[mid]
50+
51+
# nums[0] < nums[mid] -> 배열 시작점부터 mid까지는 정렬된 상태
52+
# → 회전 지점은 오른쪽에 있음 -> l을 오른쪽으로 이동
53+
if nums[0] < nums[mid]:
54+
l = mid + 1
55+
else:
56+
# 그 외의 경우 회전 지점은 왼쪽 구간에 존재
57+
h = mid - 1
58+
59+
# 회전이 아예 없는 경우(완전 정렬 상태)
60+
return nums[0]
61+
62+
# def findMin(self, nums: List[int]) -> int:
63+
# # 파이썬의 내장함수인 min을 이용해도 문제 풀이 가능
64+
# # https://wiki.python.org/moin/TimeComplexity애 의하면 min 함수도 O(n)임
65+
# return min(nums)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# 시간 복잡도: O(n)
2+
# - 트리의 모든 노드를 한 번씩 방문하면서 깊이 계산
3+
# - 각 노드는 정확히 한 번만 처리되므로 전체 노드 수 n에 비례
4+
5+
# 공간 복잡도: O(h)
6+
# - 재귀 호출 스택의 최대 깊이는 트리의 높이(h)만큼 쌓임
7+
# - 균형 트리면 O(log n), 일자로 치우친 트리(skewed tree)면 O(n)까지 갈 수 있다.
8+
9+
10+
11+
# Definition for a binary tree node.
12+
# class TreeNode:
13+
# def __init__(self, val=0, left=None, right=None):
14+
# self.val = val
15+
# self.left = left
16+
# self.right = right
17+
class Solution:
18+
# 현재 노드(cur_node)를 기준으로 왼쪽/오른쪽 서브트리의 최대 깊이를 구하고
19+
# 그 중 큰 값을 반환하는 재귀 함수을 사용
20+
def maxDepth(self, root: Optional[TreeNode]) -> int:
21+
# depth는 지금까지 내려온 깊이를 의미
22+
def find_max_depth(cur_node, depth):
23+
# 더 내려갈 노드가 없다면(leaf의 자식) 지금까지의 depth가 해당 경로의 깊이
24+
if not cur_node:
25+
return depth
26+
27+
# 왼쪽으로 한 단계 내려가면서 깊이를 증가시켜 탐색
28+
left_depth = find_max_depth(cur_node.left, depth + 1)
29+
# 오른쪽도 동일하게 탐색
30+
right_depth = find_max_depth(cur_node.right, depth + 1)
31+
32+
# 왼쪽/오른쪽 중 더 깊은 쪽이 이 노드 기준 최대 깊이
33+
return max(left_depth, right_depth)
34+
35+
# 루트에서 시작하며 깊이는 0부터 시작
36+
max_depth = find_max_depth(root, 0)
37+
38+
return max_depth

merge-two-sorted-lists/liza0525.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# 시간 복잡도: O(m + n)
2+
# - 두 연결 리스트(list1, list2)를 각각 한 번씩만 순회하면 된다.
3+
# - 각 노드를 정확히 한 번씩만 비교·이동하므로 전체 길이에 선형 시간.
4+
5+
# 공간 복잡도: O(1)
6+
# - 새로운 노드를 생성해 리스트를 복제하지 않고,
7+
# 기존 리스트 노드들을 그대로 이어붙이기 때문에 추가 메모리는 거의 없다.
8+
# - 연결 작업을 위한 dummy 노드 1개만 사용.
9+
10+
11+
12+
# Definition for singly-linked list.
13+
# class ListNode:
14+
# def __init__(self, val=0, next=None):
15+
# self.val = val
16+
# self.next = next
17+
class Solution:
18+
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
19+
# 결과 리스트의 첫 지점을 쉽게 관리하기 위해 더미(Dummy) 노드를 만든다.
20+
root_node = ListNode()
21+
cur_node = root_node # 현재 결과 리스트를 이어가는 포인터
22+
23+
# 두 리스트가 모두 남아있는 동안 반복한다.
24+
# - 각 리스트의 head를 비교해 더 작은 쪽을 결과 리스트에 붙인다.
25+
while list1 and list2:
26+
if list1.val < list2.val:
27+
# list1의 노드를 결과 리스트에 연결하고 list1 포인터를 다음으로 이동
28+
cur_node.next = list1
29+
list1 = list1.next
30+
else:
31+
# list2의 노드를 결과 리스트에 연결하고 list2 포인터를 다음으로 이동
32+
cur_node.next = list2
33+
list2 = list2.next
34+
35+
# 결과 리스트 포인터도 한 칸 전진
36+
cur_node = cur_node.next
37+
38+
# 어느 한쪽이 끝났다면, 남아 있는 리스트 전체를 그대로 이어붙인다.
39+
# - 이미 정렬되어 있으므로 추가 비교 없이 한 번에 연결 가능
40+
cur_node.next = list1 or list2
41+
42+
# dummy 노드 다음부터가 실제 머지된 리스트의 시작점
43+
return root_node.next

word-search/liza0525.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# 시간 복잡도: O(m · n · 4^k)
2+
# - 보드의 모든 칸(m·n)을 시작점으로 고려하고
3+
# - 각 칸에서 단어 길이 k만큼 DFS 탐색을 수행한다.
4+
# - 각 단계마다 최대 4방향으로 뻗을 수 있기 때문에 지수적 탐색 구조를 가진다.
5+
# (다만 visits로 중복 방문을 막아 실제 탐색량은 줄어든다.)
6+
7+
# 공간 복잡도: O(m · n)
8+
# - 방문 여부를 저장하는 2차원 배열 때문에 m·n의 공간이 필요하다.
9+
# - DFS 재귀 깊이는 최대 단어 길이 k까지 깊어질 수 있어 O(k) 스택을 추가로 사용하지만,
10+
# 전체 공간에서 보면 visits가 차지하는 비중이 더 크다.
11+
12+
13+
class Solution:
14+
def exist(self, board: List[List[str]], word: str) -> bool:
15+
# 보드의 행(row)과 열(column) 개수를 가져온다.
16+
m = len(board)
17+
n = len(board[0])
18+
19+
# 전체 칸 수보다 단어 길이가 길다면 애초에 만들 수 없으니 바로 False
20+
if len(word) > m * n:
21+
return False
22+
23+
# 방문 여부를 기록할 2차원 배열 생성
24+
# - 같은 칸은 다시 사용하지 못하므로 DFS에서 체크해야 한다.
25+
visits = [[False for _ in range(n)] for _ in range(m)]
26+
27+
# DFS 함수: (i, j)에서 word[str_index] 문자를 만족시키는지 재귀적으로 탐색한다.
28+
def dfs(i, j, str_index):
29+
# 범위 벗어나는지, 이미 방문한 칸인지, 현재 문자가 일치하는지 확인
30+
if not (
31+
0 <= i < m
32+
and 0 <= j < n
33+
and str_index < len(word)
34+
and not visits[i][j]
35+
and board[i][j] == word[str_index]
36+
):
37+
return
38+
39+
# 현재 문자가 마지막 문자라면 단어를 모두 찾은 것
40+
if str_index == len(word) - 1:
41+
return True
42+
43+
# 현재 칸 방문 처리
44+
visits[i][j] = True
45+
46+
# 상하좌우 네 방향으로 다음 문자를 찾으러 이동
47+
# - 하나라도 성공하면 그대로 True를 반환
48+
if (
49+
dfs(i + 1, j, str_index + 1)
50+
or dfs(i - 1, j, str_index + 1)
51+
or dfs(i, j + 1, str_index + 1)
52+
or dfs(i, j - 1, str_index + 1)
53+
):
54+
return True
55+
56+
# 모든 방향 실패 시 백트래킹: 방문 기록을 원복해줘야 다른 경로에서 다시 사용할 수 있다.
57+
visits[i][j] = False
58+
return False
59+
60+
# 모든 칸을 단어의 시작점으로 삼아 DFS 시도
61+
for start_i in range(m):
62+
for start_j in range(n):
63+
if dfs(start_i, start_j, 0):
64+
return True
65+
66+
# 모든 시도를 해도 못 찾으면 False
67+
return False

0 commit comments

Comments
 (0)