From c09ad1bf179c00b9c4445ee45d5a8ba8d8c5a1c5 Mon Sep 17 00:00:00 2001 From: KyungJin Jung Date: Sun, 19 Jul 2026 01:58:07 +0900 Subject: [PATCH 1/7] =?UTF-8?q?79.=20Word=20Search=20(=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EB=B3=B5=EC=9E=A1=EB=8F=84=20=EC=B5=9C=EC=A0=81=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- word-search/okyungjin.py | 90 ++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/word-search/okyungjin.py b/word-search/okyungjin.py index 84989c32aa..b17f73ad1a 100644 --- a/word-search/okyungjin.py +++ b/word-search/okyungjin.py @@ -1,89 +1,61 @@ +# https://leetcode.com/problems/word-search/ + # board: 영문 대/소문자 배열, m * n # board의 상하좌우로 인접한 문자열을 이어붙여서 word를 완성할 수 있는지 여부를 반환한다. # 단, 셀은 한번만 사용 가능하다 -# dfs를 통해 백트랙킹으로 정답을 찾도록 구현했다. +# [접근법] +# dfs로 문자열을 상,하,좌,우로 탐색한다. +# [복잡도] # L: word 의 길이 -# 시간 복잡도: O(m * n * 4^L) -# 공간 복잡도: O(m * n + L) -class SolutionA: +# 시간 복잡도: O(m * n * 3^L), 이전 문자에서 이동한 방향은 제외하므로 3^L +# 공간 복잡도: O(L) + +class Solution: def exist(self, board: List[List[str]], word: str) -> bool: row_size = len(board) col_size = len(board[0]) - # 방문 여부를 기록하는 2차원 배열 - visited = [[False] * col_size for _ in range(row_size)] - - def dfs(row, col, str_idx) -> bool: - if str_idx == len(word): - return True - - # 종료조건1: 보드의 좌표를 넘어가는 경우 - if row < 0 or row >= row_size or col < 0 or col >= col_size: + # board에 word를 만들 수 있는 문자열이 충분히 있는지 확인 + for char in set(word): + # board 전체에서 해당 알파벳이 몇 개인지 카운트한다. + board_char_count = sum(row.count(char) for row in board) + if board_char_count < word.count(char): return False - - # 종료조건 2: 이미 방문함 - # 종료조건 3: 문자열 불일치 - if visited[row][col] or board[row][col] != word[str_idx]: - return False - - - visited[row][col] = True - - # 차례대로 상, 하, 좌, 우의 좌표를 탐색 - found = (dfs(row - 1, col, str_idx + 1) or \ - dfs(row + 1, col, str_idx + 1) or \ - dfs(row, col - 1, str_idx + 1) or \ - dfs(row, col + 1, str_idx + 1)) - - visited[row][col] = False # 백트래킹 - - return found + # 빈도수가 더 적은 글자부터 탐색하도록 단어를 뒤집는다 + count_first = sum(row.count(word[0]) for row in board) + count_last = sum(row.count(word[-1]) for row in board) + + if count_first > count_last: + word = word[::-1] - # 시작점을 찾는다 - for r in range(row_size): - for c in range(col_size): - if board[r][c] == word[0] and dfs(r, c, 0): - return True - - return False - -# SolutionA 의 공간복잡도 최적화 버전, visited 제거 -# L: word 의 길이 -# 시간 복잡도: O(m * n * 4^L) -# 공간 복잡도: O(L) -class Solution: - def exist(self, board: List[List[str]], word: str) -> bool: VISITED_MARK = '#' - - row_size = len(board) - col_size = len(board[0]) - - def dfs(row, col, str_idx) -> bool: + def dfs(row: int, col: int, str_idx: int) -> bool: if str_idx == len(word): return True # 종료조건1: 보드의 좌표를 넘어가는 경우 - if row < 0 or row >= row_size or col < 0 or col >= col_size: + if not (0 <= row < row_size and 0 <= col < col_size): return False - # 종료조건 2: 이미 방문함 + # 종료조건 2: 이미 방문함 (#: 방문했음을 표시하는 마크) # 종료조건 3: 문자열 불일치 if board[row][col] == VISITED_MARK or board[row][col] != word[str_idx]: return False - - # VISITED_MARK로 교체하여 방문을 기록 + # 방문을 표시하는 마크(#)로 임시 교체 origin_char = board[row][col] board[row][col] = VISITED_MARK - # 차례대로 상, 하, 좌, 우의 좌표를 탐색 - found = (dfs(row - 1, col, str_idx + 1) or \ - dfs(row + 1, col, str_idx + 1) or \ - dfs(row, col - 1, str_idx + 1) or \ - dfs(row, col + 1, str_idx + 1)) + # 좌표를 탐색한다 + found = ( + dfs(row - 1, col, str_idx + 1) # 상 + or dfs(row + 1, col, str_idx + 1) # 하 + or dfs(row, col - 1, str_idx + 1) # 좌 + or dfs(row, col + 1, str_idx + 1) # 우 + ) # 원본 문자열로 복원 board[row][col] = origin_char From e045b606ad3bf3bdcb3d2155c7619c1120bd9f27 Mon Sep 17 00:00:00 2001 From: KyungJin Jung Date: Sun, 19 Jul 2026 17:39:55 +0900 Subject: [PATCH 2/7] =?UTF-8?q?322.=20Coin=20Change=20(=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EB=B3=B5=EC=9E=A1=EB=8F=84=20=EC=B5=9C=EC=A0=81=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- coin-change/okyungjin.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/coin-change/okyungjin.py b/coin-change/okyungjin.py index 5941cb14e7..f78b866745 100644 --- a/coin-change/okyungjin.py +++ b/coin-change/okyungjin.py @@ -5,8 +5,6 @@ # coins로 amount를 만들 수 없는 경우 -1을 반환 # 동전의 종류는 무한대라고 가정하고 문제를 풀 것 -# 처음에는 greedy로 접근하려고 했는데, 금액이 큰 동전을 먼저 선택하는게 최소 동전의 수가 아니라서 dp로 풀이했다. - # [복잡도] # C: 코인의 개수, # A: amount # 시간 복잡도: O(A * C) @@ -19,21 +17,19 @@ def coinChange(self, coins: List[int], amount: int) -> int: # min_coins[i]: i원을 만드는데 필요한 최소 동전의 개수 min_coins = [NOT_POSSIBLE] * (amount + 1) min_coins[0] = 0 - - # 최적화를 위해 정렬 - coins.sort() + # 1부터 목표 금액(amount)까지 상향식으로 최소 동전 개수를 계산 for cur_amount in range(1, amount + 1): - # coin: 선택한 동전의 금액 + # 모든 동전 종류를 확인하여 현재 금액을 만들 수 있는 조합 탐색 for coin in coins: - # coins가 정렬되어 있으므로, cur_amount 보다 동전의 금액이 더 크면 이후는 확인 불필요 - if coin > cur_amount: - break - - # 둘 중 더 작은 값으로 업데이트 - # 1. 기존에 구한 개수 - # 2. 현재 동전을 1개 추가해서 만드는 개수 - min_coins[cur_amount] = min(min_coins[cur_amount], min_coins[cur_amount - coin] + 1) + # 동전 금액이 현재 금액보다 작거나 같을 때만 유효한 조합으로 간주 + if coin <= cur_amount: + # '남은 금액을 만드는 최소 개수' + '현재 동전 1개'를 합산 + new_val = min_coins[cur_amount - coin] + 1 + + # 더 적은 개수의 동전 조합을 찾았다면 해당 금액의 최솟값을 갱신 + if new_val < min_coins[cur_amount]: + min_coins[cur_amount] = new_val # 초기화된 값 그대로이면 불가능한 조합이므로 -1 반환 if min_coins[amount] == NOT_POSSIBLE: From d6459ccb8269924491f79bbe1a439e3dde310bba Mon Sep 17 00:00:00 2001 From: KyungJin Jung Date: Sun, 19 Jul 2026 17:52:38 +0900 Subject: [PATCH 3/7] =?UTF-8?q?21.=20Merge=20Two=20Sorted=20Lists=20(?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EB=B3=B5=EC=9E=A1=EB=8F=84=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- merge-two-sorted-lists/okyungjin.py | 114 ++++------------------------ 1 file changed, 16 insertions(+), 98 deletions(-) diff --git a/merge-two-sorted-lists/okyungjin.py b/merge-two-sorted-lists/okyungjin.py index b1a704b56a..2722685ff5 100644 --- a/merge-two-sorted-lists/okyungjin.py +++ b/merge-two-sorted-lists/okyungjin.py @@ -1,110 +1,28 @@ -# from typing import Optional, List - -# class ListNode: -# def __init__(self, val=0, next=None): -# self.val = val -# self.next = next - -# # 파이썬의 toString 역할을 하는 __repr__을 [1,3,4] 형태로 출력되게 수정 -# def __repr__(self): -# nodes = [] -# curr = self -# while curr: -# nodes.append(str(curr.val)) -# curr = curr.next -# return "[" + ",".join(nodes) + "]" - -# # 파이썬 list를 ListNode 리스트로 변환해주는 헬퍼 함수 -# def make_linked_list(arr: List[int]) -> Optional[ListNode]: -# if not arr: -# return None -# dummy = ListNode(0) -# curr = dummy -# for val in arr: -# curr.next = ListNode(val) -# curr = curr.next -# return dummy.next - - # list1의 노드 개수를 n, list2의 노드 개수를 m이라 할 때 # 시간 복잡도: O(n + m) -# 공간 복잡도: O(n + m) -class Solution_01: +# 공간 복잡도: O(1) -> dummy만 사용, 추가 공간 복잡도는 O(1) +class Solution: def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - # 맨 앞에 더미 노드를 하나 추가한다 - # 없는 상태로 짰는데 if node: else: 구문 늘어나서 맨 앞에 가상의 노드를 하나 추가해줬다 + # dummy: 병합된 결과 리스트의 시작점을 보존하기 위한 가상의 헤드 노드 + # current: 현재 병합 위치를 추적하며 노드를 연결해 나가는 포인터 dummy = ListNode(None) - node = dummy + current = dummy - while True: - # list1이 비어있으면 현재 노드 뒤로 list2를 이어준다. - if not list1: - node.next = list2 - break - - # list2가 비어있으면 현재 노드 뒤로 list1을 이어준다. - if not list2: - node.next = list1 - break - - # 값을 비교해서 작은 값을 next 노드에 추가한다. + # 두 리스트가 모두 존재하는 동안 값을 비교하며 merge한다 + while list1 and list2: + # 더 작은 값을 가진 노드를 결과 리스트에 연결 if list1.val <= list2.val: - node.next = ListNode(list1.val) - # list1 하나 소비 + current.next = list1 list1 = list1.next else: - node.next = ListNode(list2.val) - # list2 하나 소비 + current.next = list2 list2 = list2.next - - # 다음 노드를 현재 노드로 할당해준다 - node = node.next - - return dummy.next - -# [접근법] Solution_01 의 공간 복잡도를 개선했습니다. -# ListNode를 새로 생성하지 않고 기존 노드를 활용하도록 수정했습니다. - -# list1의 노드 개수를 n, list2의 노드 개수를 m이라 할 때 -# 시간 복잡도: O(n + m) -# 공간 복잡도: O(1) -> dummy만 사용, 추가 공간 복잡도는 O(1) -class Solution: - def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: - # 맨 앞에 더미 노드를 하나 추가한다 - # 없는 상태로 짰는데 if node: else: 구문 늘어나서 맨 앞에 가상의 노드를 하나 추가해줬다 - dummy = ListNode(None) - node = dummy + # 병합 리스트의 포인터를 다음으로 이동 + current = current.next - while True: - if not list1: # list1이 비어있으면 현재 노드 뒤로 list2를 이어준다. - node.next = list2 - break - - if not list2: # list2가 비어있으면 현재 노드 뒤로 list1을 이어준다. - node.next = list1 - break - - # 값을 비교해서 작은 값을 next 노드에 추가한다. - if list1.val <= list2.val: - node.next = list1 # 새로운 ListNode를 생성하지 않고 list1를 할당해준다. - list1 = list1.next # list1 하나 소비 - else: - node.next = list2 # 새로운 ListNode를 생성하지 않고 list2를 할당해준다. - list2 = list2.next # list2 하나 소비 - - # 다음 노드를 현재 노드로 할당해준다 - node = node.next - + # 한쪽 리스트가 소진되면 남은 노드들을 한 번에 연결 + current.next = list1 if list1 else list2 + + # dummy 노드의 다음부터가 실제 정렬된 리스트의 시작 return dummy.next - - -# 4. 예시 입력으로 호출 및 테스트 -# if __name__ == "__main__": -# list1 = make_linked_list([1, 2, 4]) -# list2 = make_linked_list([1, 3, 4]) - -# solution = Solution() -# merged_list = solution.mergeTwoLists(list1, list2) - -# print(merged_list) From 3e0f2b8743d3a680b8981d8882c6f98378aae05d Mon Sep 17 00:00:00 2001 From: KyungJin Jung Date: Sun, 19 Jul 2026 18:00:14 +0900 Subject: [PATCH 4/7] =?UTF-8?q?153.=20Find=20Minimum=20in=20Rotated=20Sort?= =?UTF-8?q?ed=20Array=20(=EC=8B=9C=EA=B0=84=20=EB=B3=B5=EC=9E=A1=EB=8F=84?= =?UTF-8?q?=20=EC=B5=9C=EC=A0=81=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- find-minimum-in-rotated-sorted-array/okyungjin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/find-minimum-in-rotated-sorted-array/okyungjin.py b/find-minimum-in-rotated-sorted-array/okyungjin.py index bbcdf18b9f..2e017d6383 100644 --- a/find-minimum-in-rotated-sorted-array/okyungjin.py +++ b/find-minimum-in-rotated-sorted-array/okyungjin.py @@ -12,11 +12,13 @@ # 공간 복잡도: O(1) class Solution: def findMin(self, nums: List[int]) -> int: - - # 왼쪽/오른쪽 값의 인덱스 계산 left = 0 right = len(nums) - 1 + # 이미 정렬된 상태인 경우 or n번 회전한 경우 분기 처리 + if nums[left] <= nums[right]: + return nums[left] + while left < right: # 중간값 인덱스 mid = (left + right) // 2 From 5723acb44bf10dc9ec273c1c6f540cbf51e8150c Mon Sep 17 00:00:00 2001 From: KyungJin Jung Date: Sun, 19 Jul 2026 18:10:55 +0900 Subject: [PATCH 5/7] =?UTF-8?q?104.=20Maximum=20Depth=20of=20Binary=20Tree?= =?UTF-8?q?=20(BFS=20=ED=92=80=EC=9D=B4=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- maximum-depth-of-binary-tree/okyungjin.py | 54 +++++++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/maximum-depth-of-binary-tree/okyungjin.py b/maximum-depth-of-binary-tree/okyungjin.py index 98a1a00f39..4e70dbdf6d 100644 --- a/maximum-depth-of-binary-tree/okyungjin.py +++ b/maximum-depth-of-binary-tree/okyungjin.py @@ -1,28 +1,64 @@ -# 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 - -# 시간 복잡도: O(N), 모든 노드를 한번 씩 방문 -# 공간 복잡도: O(N), 최악의 경우 모든 노드가 큐에 담길 수 있음 +''' +스택을 활용한 DFS + +시간 복잡도: O(N) +공간 복잡도: O(H), H: 트리의 높이 +''' class Solution: def maxDepth(self, root: Optional[TreeNode]) -> int: + # 트리가 비어있으면 길이는 0 if root is None: return 0 max_depth = 1 + + # 스택에 (현재 노드, 노드의 깊이) 튜플을 저장 stack = [(root, 1)] while stack: node, cur_depth = stack.pop() + + # 현재 노드의 깊이와 저장된 최댓값을 비교하여 갱신 max_depth = max(cur_depth, max_depth) + # 왼쪽 자식 노드가 있다면 현재 깊이 + 1을 하여 스택에 추가 if node.left: stack.append((node.left, cur_depth + 1)) + # 오른쪽 자식 노드가 있다면 현재 깊이 + 1을 하여 스택에 추가 if node.right: stack.append((node.right, cur_depth + 1)) return max_depth + +''' +큐를 활용한 BFS + +시간 복잡도: O(N) +공간 복잡도: O(W), W: 트리의 최대 너비 +''' +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + # 트리가 비어있으면 길이는 0 + if root is None: + return 0 + + queue = deque([root]) + depth = 0 + + while queue: + # 현재 레벨에 있는 노드의 개수만큼 반복 처리 + level_size = len(queue) + + for _ in range(level_size): + node = queue.popleft() + + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + + # 한 레벨의 탐색이 끝나면 깊이 증가 + depth += 1 + + return depth From f0245ab0f70ef6dbffc6a07a5264a50343784b85 Mon Sep 17 00:00:00 2001 From: KyungJin Jung Date: Sun, 19 Jul 2026 21:27:04 +0900 Subject: [PATCH 6/7] =?UTF-8?q?104.=20Maximum=20Depth=20of=20Binary=20Tree?= =?UTF-8?q?=20(=EC=9E=AC=EA=B7=80=20=ED=95=A8=EC=88=98=20=ED=92=80?= =?UTF-8?q?=EC=9D=B4=20=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- maximum-depth-of-binary-tree/okyungjin.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/maximum-depth-of-binary-tree/okyungjin.py b/maximum-depth-of-binary-tree/okyungjin.py index 4e70dbdf6d..89a1f7e31b 100644 --- a/maximum-depth-of-binary-tree/okyungjin.py +++ b/maximum-depth-of-binary-tree/okyungjin.py @@ -62,3 +62,20 @@ def maxDepth(self, root: Optional[TreeNode]) -> int: depth += 1 return depth + +''' +재귀 함수 풀이 + +시간 복잡도: O(N) +공간 복잡도: O(H), H: 스택의 높이 +''' +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + if root is None: + return 0 + + left_depth = self.maxDepth(root.left) + right_depth = self.maxDepth(root.right) + + # 현재 자신의 노드 하나를 더해서 최대 깊이를 반환 + return max(left_depth, right_depth) + 1 \ No newline at end of file From 071f547208296af54ee25c935f2bcce7884c425d Mon Sep 17 00:00:00 2001 From: KyungJin Jung Date: Sun, 19 Jul 2026 21:27:32 +0900 Subject: [PATCH 7/7] 104. Maximum Depth of Binary Tree --- maximum-depth-of-binary-tree/okyungjin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maximum-depth-of-binary-tree/okyungjin.py b/maximum-depth-of-binary-tree/okyungjin.py index 89a1f7e31b..3f03e9631f 100644 --- a/maximum-depth-of-binary-tree/okyungjin.py +++ b/maximum-depth-of-binary-tree/okyungjin.py @@ -78,4 +78,4 @@ def maxDepth(self, root: Optional[TreeNode]) -> int: right_depth = self.maxDepth(root.right) # 현재 자신의 노드 하나를 더해서 최대 깊이를 반환 - return max(left_depth, right_depth) + 1 \ No newline at end of file + return max(left_depth, right_depth) + 1