diff --git a/coin-change/unpo88.py b/coin-change/unpo88.py new file mode 100644 index 0000000000..46c3e425b5 --- /dev/null +++ b/coin-change/unpo88.py @@ -0,0 +1,82 @@ +class Solution: + def coinChange(self, coins: list[int], amount: int) -> int: + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + + for coin in coins: + for x in range(coin, amount + 1): + dp[x] = min(dp[x], dp[x - coin] + 1) + + return dp[amount] if dp[amount] != float('inf') else -1 + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] 완전 탐색으로 접근하면? +──────────────────────────────────────────────────────────────────────────────── +1. 모든 동전 조합을 시도해보면 어떨까? +2. coins = [1, 2, 5], amount = 11 + - 5 + 5 + 1 = 11 (3개) + - 5 + 2 + 2 + 2 = 11 (4개) + - 1 + 1 + ... (11개) + ... 너무 많은 경우의 수! + +3. 문제: 시간복잡도가 너무 높음 (지수 시간) +4. 더 효율적인 방법이 필요함 → DP로 접근하자! + +──────────────────────────────────────────────────────────────────────────────── +[2차 시도] DP 초기화와 점화식 +──────────────────────────────────────────────────────────────────────────────── +5. dp[i] = i원을 만드는데 필요한 최소 동전 개수 +6. 초기화: + - dp[0] = 0 (0원 만들기 = 동전 0개) + - dp[1~amount] = 아직 계산 안 됨 + + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + +7. 점화식: + - 각 동전 coin에 대해 + - dp[x] = min(dp[x], dp[x - coin] + 1) + - 의미: "x원 = (x-coin)원 + coin 1개" + +8. Eample) coins = [1, 2, 5], amount = 11 + + 초기: dp = [0, inf, inf, inf, ..., inf] + + 동전 1 처리: + dp[1] = min(inf, dp[0]+1) = 1 + dp[2] = min(inf, dp[1]+1) = 2 + dp[3] = min(inf, dp[2]+1) = 3 + ... + + 동전 2 처리: + dp[2] = min(2, dp[0]+1) = 1 # 2원 동전 1개! + dp[3] = min(3, dp[1]+1) = 2 # 2+1 + dp[4] = min(4, dp[2]+1) = 2 # 2+2 + ... + + 동전 5 처리: + dp[5] = min(5, dp[0]+1) = 1 # 5원 동전 1개! + dp[6] = min(6, dp[1]+1) = 2 # 5+1 + dp[10] = min(10, dp[5]+1) = 2 # 5+5 + dp[11] = min(11, dp[6]+1) = 3 # 5+5+1 + + +[최종 구현] Bottom-Up DP +──────────────────────────────────────────────────────────────────────────────── +12. 모든 동전에 대해 반복 +13. 각 동전으로 만들 수 있는 모든 금액 업데이트 +14. 불가능하면 -1 반환 (dp[amount]가 여전히 무한대) + + for coin in coins: + for x in range(coin, amount + 1): + dp[x] = min(dp[x], dp[x - coin] + 1) + + return dp[amount] if dp[amount] != float('inf') else -1 + +15. 시간복잡도: O(amount × coins) - 효율적! +16. 공간복잡도: O(amount) - dp 배열 +""" diff --git a/find-minimum-in-rotated-sorted-array/unpo88.py b/find-minimum-in-rotated-sorted-array/unpo88.py new file mode 100644 index 0000000000..51aec75526 --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/unpo88.py @@ -0,0 +1,38 @@ +class Solution: + def findMin(self, nums: List[int]) -> int: + left, right = 0, len(nums) - 1 + + while left < right: + mid = (left + right) // 2 + + if nums[mid] > nums[right]: + left = mid + 1 + else: + right = mid + + return nums[left] + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] 이진 탐색 적용 - 기본 구조 +──────────────────────────────────────────────────────────────────────────────── +1. log(n) 시간 복잡도를 만족시키는 이진 탐색 구조로 최소값을 찾으면 될 것 같음 + + left, right = 0, len(nums) - 1 + + while left < right: + mid = (left + right) // 2 + + if nums[mid] > nums[right]: + left = mid + 1 + else: + right = mid + + return nums[left] + +2. 시간복잡도: O(log n) - 이진 탐색 +3. 공간복잡도: O(1) - 추가 공간 사용 안 함 +""" diff --git a/maximum-depth-of-binary-tree/unpo88.py b/maximum-depth-of-binary-tree/unpo88.py new file mode 100644 index 0000000000..2ac3205a9f --- /dev/null +++ b/maximum-depth-of-binary-tree/unpo88.py @@ -0,0 +1,52 @@ +# 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(self, root: Optional[TreeNode]) -> int: + if not root: + return 0 + + left_depth = self.maxDepth(root.left) + right_depth = self.maxDepth(root.right) + + return max(left_depth, right_depth) + 1 + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] 재귀로 깊이 카운트 - None은 0 +──────────────────────────────────────────────────────────────────────────────── +1. 빈 노드(None)는 깊이가 0이 +2. leaf 노드에서 양쪽 자식이 None이면 둘 다 0을 반환 +3. 그러면 leaf 노드는 max(0, 0) + 1 = 1이 됨 (자기 자신만 카운트) + + def maxDepth(self, root): + if not root: + return 0 # 빈 노드는 0 + + left = self.maxDepth(root.left) + right = self.maxDepth(root.right) + + return max(left, right) + 1 # 더 깊은 쪽 + 나 자신(1) + +4. 동작 예시: + 트리: 1 + / \ + 2 3 + / + 4 + + maxDepth(4) → max(0, 0) + 1 = 1 + maxDepth(2) → max(1, 0) + 1 = 2 + maxDepth(3) → max(0, 0) + 1 = 1 + maxDepth(1) → max(2, 1) + 1 = 3 ✓ + + +5. 시간복잡도: O(n) - 모든 노드를 1번씩 방문 +6. 공간복잡도: O(h) - 재귀 스택, h는 트리 높이 +""" diff --git a/merge-two-sorted-lists/unpo88.py b/merge-two-sorted-lists/unpo88.py new file mode 100644 index 0000000000..4af349e45e --- /dev/null +++ b/merge-two-sorted-lists/unpo88.py @@ -0,0 +1,55 @@ +# 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 = ListNode() + head = dummy + + while list1 and list2: + if list1.val < list2.val: + head.next = list1 + list1 = list1.next + else: + head.next = list2 + list2 = list2.next + head = head.next + + if list1: + head.next = list1 + elif list2: + head.next = list2 + + return dummy.next + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] Dummy Node 활용 +──────────────────────────────────────────────────────────────────────────────── +1. 작은 값을 가진 노드를 골라서 head에 연결하는 방식으로 문제를 풀어보자. +2. 한 쪽 리스트가 비게 되면 남은 노드를 그냥 연결해주면 될 것 같음. + + dummy = ListNode() + head = dummy + + while list1 and list2: + if list1.val < list2.val: + head.next = list1 + list1 = list1.next + else: + head.next = list2 + list2 = list2.next + head = head.next + + if list1: + head.next = list1 + elif list2: + head.next = list2 + + return dummy.next +""" diff --git a/word-search/unpo88.py b/word-search/unpo88.py new file mode 100644 index 0000000000..37d1c4b033 --- /dev/null +++ b/word-search/unpo88.py @@ -0,0 +1,111 @@ +class Solution: + def exist(self, board: list[list[str]], word: str) -> bool: + if not board or not board[0]: + return False + + m, n = len(board), len(board[0]) + + dx = [-1, 1, 0, 0] + dy = [0, 0, -1, 1] + + def dfs(x, y, index): + if index == len(word): + return True + + if x < 0 or x >= m or y < 0 or y >= n or board[x][y] != word[index]: + return False + + temp = board[x][y] + board[x][y] = '#' + + for i in range(4): + nx = x + dx[i] + ny = y + dy[i] + if dfs(nx, ny, index + 1): + return True + + board[x][y] = temp + return False + + for i in range(m): + for j in range(n): + if board[i][j] == word[0] and dfs(i, j, 0): + return True + + return False + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] DFS + 방향 배열로 접근 +──────────────────────────────────────────────────────────────────────────────── +1. 격자에서 상하좌우로 이동하며 단어를 찾아야 함 +2. 같은 셀은 한 번만 사용 가능 → 방문 체크 필요 +3. DFS(깊이 우선 탐색) + 백트래킹으로 풀면 될 것 같음 +4. 상하좌우 이동을 위한 dx, dy 배열 만들자 + + dx = [-1, 1, 0, 0] + dy = [0, 0, -1, 1] + + +[2차 시도] DFS 함수 구조 설계 +──────────────────────────────────────────────────────────────────────────────── +5. dfs(x, y, index) 형태로 현재 위치와 단어의 인덱스를 추적 +6. base case: + - index == len(word): 단어 끝까지 찾음 → True + - 범위 벗어남 or 문자 불일치 → False + + def dfs(x, y, index): + if index == len(word): + return True + + if x < 0 or x >= m or y < 0 or y >= n or board[x][y] != word[index]: + return False + +7. 방문한 셀은 어떻게 표시하지? + + +[3차 시도] 방문 표시와 백트래킹 +──────────────────────────────────────────────────────────────────────────────── +8. 현재 셀을 '#' 같은 특수 문자로 임시 변경 (방문 표시) +9. 4방향으로 재귀 탐색 +10. 탐색 실패 시 원래 값으로 복원 (백트래킹) + + temp = board[x][y] + board[x][y] = '#' + + for i in range(4): + nx = x + dx[i] + ny = y + dy[i] + if dfs(nx, ny, index + 1): + return True + + board[x][y] = temp + return False + + +[4차 시도] 조기 종료 최적화 추가 +──────────────────────────────────────────────────────────────────────────────── +11. 빈 보드는 바로 False 반환 +12. 첫 글자가 일치하는 셀에서만 DFS 시작 (불필요한 탐색 방지) + + if not board or not board[0]: + return False + + for i in range(m): + for j in range(n): + if board[i][j] == word[0] and dfs(i, j, 0): + return True + + +[최종 구현] 최적화된 DFS 탐색 +──────────────────────────────────────────────────────────────────────────────── +13. 조기 종료로 불필요한 탐색 제거 +14. 백트래킹으로 방문 상태 관리 +15. 하나라도 성공하면 즉시 True 반환 + +16. 시간복잡도: O(m * n * 4^L) - 최악의 경우, 조기 종료로 실제로는 더 빠름 +17. 공간복잡도: O(L) - 재귀 깊이 +"""