Skip to content
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
65 changes: 65 additions & 0 deletions find-minimum-in-rotated-sorted-array/liza0525.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
class Solution:
def findMin(self, nums: List[int]) -> int:
# 선형 탐색(linear scan) 버전

# 시간 복잡도: O(n)
# - 배열 전체를 뒤에서 앞으로 한 칸씩 순회하면서
# "회전이 끊기는 지점"을 찾는다.
# - 최악의 경우 n-1번 비교.

# 공간 복잡도: O(1)
# - 추가 변수만 사용.

# 배열 길이가 1개면 그 값 자체가 최솟값
min_num = 0
if len(nums) == 1:
return nums[0]

# 뒤에서 앞으로 탐색하면서
# nums[i] < nums[i-1] 지점(회전이 일어난 지점)을 찾는다.
# 해당 지점의 nums[i]가 최솟값
for i in range(len(nums) - 1, -1, -1):
if nums[i] < nums[i - 1]:
min_num = nums[i]
break

return min_num

def findMinBinarySearch(self, nums: List[int]) -> int:
# 이진 탐색(binary search) 버전

# 시간 복잡도: O(log n)
# - 정렬된 배열이 한 번 회전(rotated)된 형태를 이용해
# 절반씩 탐색 범위를 줄인다.
# - mid 기준 왼쪽/오른쪽 어느 쪽이 정렬 상태인지에 따라 탐색 방향 결정

# 공간 복잡도: O(1)
# - 포인터(l, h, mid)만 사용

# l은 1부터 시작하는데,
# mid-1 비교를 안전하게 하기 위함이다.
l, h = 1, len(nums) - 1

while l <= h:
mid = (l + h) // 2

# mid가 최솟값이 되는 전형적인 조건:
# mid 바로 이전 요소가 mid보다 크면 회전 지점
if nums[mid - 1] > nums[mid]:
return nums[mid]

# nums[0] < nums[mid] -> 배열 시작점부터 mid까지는 정렬된 상태
# → 회전 지점은 오른쪽에 있음 -> l을 오른쪽으로 이동
if nums[0] < nums[mid]:
l = mid + 1
else:
# 그 외의 경우 회전 지점은 왼쪽 구간에 존재
h = mid - 1

# 회전이 아예 없는 경우(완전 정렬 상태)
return nums[0]

# def findMin(self, nums: List[int]) -> int:
# # 파이썬의 내장함수인 min을 이용해도 문제 풀이 가능
# # https://wiki.python.org/moin/TimeComplexity애 의하면 min 함수도 O(n)임
# return min(nums)
38 changes: 38 additions & 0 deletions maximum-depth-of-binary-tree/liza0525.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 시간 복잡도: O(n)
# - 트리의 모든 노드를 한 번씩 방문하면서 깊이 계산
# - 각 노드는 정확히 한 번만 처리되므로 전체 노드 수 n에 비례

# 공간 복잡도: O(h)
# - 재귀 호출 스택의 최대 깊이는 트리의 높이(h)만큼 쌓임
# - 균형 트리면 O(log n), 일자로 치우친 트리(skewed tree)면 O(n)까지 갈 수 있다.



# 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:
# 현재 노드(cur_node)를 기준으로 왼쪽/오른쪽 서브트리의 최대 깊이를 구하고
# 그 중 큰 값을 반환하는 재귀 함수을 사용
def maxDepth(self, root: Optional[TreeNode]) -> int:
# depth는 지금까지 내려온 깊이를 의미
def find_max_depth(cur_node, depth):
# 더 내려갈 노드가 없다면(leaf의 자식) 지금까지의 depth가 해당 경로의 깊이
if not cur_node:
return depth

# 왼쪽으로 한 단계 내려가면서 깊이를 증가시켜 탐색
left_depth = find_max_depth(cur_node.left, depth + 1)
# 오른쪽도 동일하게 탐색
right_depth = find_max_depth(cur_node.right, depth + 1)

# 왼쪽/오른쪽 중 더 깊은 쪽이 이 노드 기준 최대 깊이
return max(left_depth, right_depth)

# 루트에서 시작하며 깊이는 0부터 시작
max_depth = find_max_depth(root, 0)

return max_depth
43 changes: 43 additions & 0 deletions merge-two-sorted-lists/liza0525.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 시간 복잡도: O(m + n)
# - 두 연결 리스트(list1, list2)를 각각 한 번씩만 순회하면 된다.
# - 각 노드를 정확히 한 번씩만 비교·이동하므로 전체 길이에 선형 시간.

# 공간 복잡도: O(1)
# - 새로운 노드를 생성해 리스트를 복제하지 않고,
# 기존 리스트 노드들을 그대로 이어붙이기 때문에 추가 메모리는 거의 없다.
# - 연결 작업을 위한 dummy 노드 1개만 사용.



# 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]:
# 결과 리스트의 첫 지점을 쉽게 관리하기 위해 더미(Dummy) 노드를 만든다.
root_node = ListNode()
cur_node = root_node # 현재 결과 리스트를 이어가는 포인터

# 두 리스트가 모두 남아있는 동안 반복한다.
# - 각 리스트의 head를 비교해 더 작은 쪽을 결과 리스트에 붙인다.
while list1 and list2:
if list1.val < list2.val:
# list1의 노드를 결과 리스트에 연결하고 list1 포인터를 다음으로 이동
cur_node.next = list1
list1 = list1.next
else:
# list2의 노드를 결과 리스트에 연결하고 list2 포인터를 다음으로 이동
cur_node.next = list2
list2 = list2.next

# 결과 리스트 포인터도 한 칸 전진
cur_node = cur_node.next

# 어느 한쪽이 끝났다면, 남아 있는 리스트 전체를 그대로 이어붙인다.
# - 이미 정렬되어 있으므로 추가 비교 없이 한 번에 연결 가능
cur_node.next = list1 or list2

# dummy 노드 다음부터가 실제 머지된 리스트의 시작점
return root_node.next
67 changes: 67 additions & 0 deletions word-search/liza0525.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 시간 복잡도: O(m · n · 4^k)
# - 보드의 모든 칸(m·n)을 시작점으로 고려하고
# - 각 칸에서 단어 길이 k만큼 DFS 탐색을 수행한다.
# - 각 단계마다 최대 4방향으로 뻗을 수 있기 때문에 지수적 탐색 구조를 가진다.
# (다만 visits로 중복 방문을 막아 실제 탐색량은 줄어든다.)

# 공간 복잡도: O(m · n)
# - 방문 여부를 저장하는 2차원 배열 때문에 m·n의 공간이 필요하다.
# - DFS 재귀 깊이는 최대 단어 길이 k까지 깊어질 수 있어 O(k) 스택을 추가로 사용하지만,
# 전체 공간에서 보면 visits가 차지하는 비중이 더 크다.


class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
# 보드의 행(row)과 열(column) 개수를 가져온다.
m = len(board)
n = len(board[0])

# 전체 칸 수보다 단어 길이가 길다면 애초에 만들 수 없으니 바로 False
if len(word) > m * n:
return False

# 방문 여부를 기록할 2차원 배열 생성
# - 같은 칸은 다시 사용하지 못하므로 DFS에서 체크해야 한다.
visits = [[False for _ in range(n)] for _ in range(m)]

# DFS 함수: (i, j)에서 word[str_index] 문자를 만족시키는지 재귀적으로 탐색한다.
def dfs(i, j, str_index):
# 범위 벗어나는지, 이미 방문한 칸인지, 현재 문자가 일치하는지 확인
if not (
0 <= i < m
and 0 <= j < n
and str_index < len(word)
and not visits[i][j]
and board[i][j] == word[str_index]
):
return

# 현재 문자가 마지막 문자라면 단어를 모두 찾은 것
if str_index == len(word) - 1:
return True

# 현재 칸 방문 처리
visits[i][j] = True

# 상하좌우 네 방향으로 다음 문자를 찾으러 이동
# - 하나라도 성공하면 그대로 True를 반환
if (
dfs(i + 1, j, str_index + 1)
or dfs(i - 1, j, str_index + 1)
or dfs(i, j + 1, str_index + 1)
or dfs(i, j - 1, str_index + 1)
):
return True

# 모든 방향 실패 시 백트래킹: 방문 기록을 원복해줘야 다른 경로에서 다시 사용할 수 있다.
visits[i][j] = False
return False

# 모든 칸을 단어의 시작점으로 삼아 DFS 시도
for start_i in range(m):
for start_j in range(n):
if dfs(start_i, start_j, 0):
return True

# 모든 시도를 해도 못 찾으면 False
return False