diff --git a/coin-change/okyungjin.py b/coin-change/okyungjin.py new file mode 100644 index 0000000000..5941cb14e7 --- /dev/null +++ b/coin-change/okyungjin.py @@ -0,0 +1,43 @@ +# coins: 동전의 가격이 적힌 정수 배열, 각 숫자는 유니크함, 정렬에 대한 언급 X, (1 <= coins.length <= 12) +# amount: 돈의 총합 + +# amount를 만들 수 있는 가장 적은 동전의 수를 반환한다. +# coins로 amount를 만들 수 없는 경우 -1을 반환 +# 동전의 종류는 무한대라고 가정하고 문제를 풀 것 + +# 처음에는 greedy로 접근하려고 했는데, 금액이 큰 동전을 먼저 선택하는게 최소 동전의 수가 아니라서 dp로 풀이했다. + +# [복잡도] +# C: 코인의 개수, # A: amount +# 시간 복잡도: O(A * C) +# 공간 복잡도: O(A) +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + NOT_POSSIBLE = amount + 1 + + # min_coins: 금액을 만드는데 필요한 동전 개수를 저장하는 배열 + # min_coins[i]: i원을 만드는데 필요한 최소 동전의 개수 + min_coins = [NOT_POSSIBLE] * (amount + 1) + min_coins[0] = 0 + + # 최적화를 위해 정렬 + coins.sort() + + 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) + + # 초기화된 값 그대로이면 불가능한 조합이므로 -1 반환 + if min_coins[amount] == NOT_POSSIBLE: + return -1 + # 최소 동전의 개수를 반환 + else: + return min_coins[amount] diff --git a/find-minimum-in-rotated-sorted-array/okyungjin.py b/find-minimum-in-rotated-sorted-array/okyungjin.py new file mode 100644 index 0000000000..bbcdf18b9f --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/okyungjin.py @@ -0,0 +1,32 @@ +# [문제 분석] +# nums: 오름차순 정렬, 정수 배열, 중복 없음 +# 배열에서 가장 작은 수를 찾아 반환 +# 조건: O(log n)으로 작성할 것 + +# [접근법] +# 정렬된 배열이 주어지고, 배열이 n번 회전한다. 추세가 꺾이는 부분을 찾아 탐색 범위를 좁힌다. +# 배열이 정렬되어 있다는 조건이 있으므로 이진 탐색을 통해 탐색 범위를 좁혀 log n으로 탐색이 가능하다. + +# [복잡도] +# 시간 복잡도: O(log n), 이진 탐색을 하므로 log n +# 공간 복잡도: O(1) +class Solution: + def findMin(self, nums: List[int]) -> int: + + # 왼쪽/오른쪽 값의 인덱스 계산 + left = 0 + right = len(nums) - 1 + + while left < right: + # 중간값 인덱스 + mid = (left + right) // 2 + + # 중간값과 오른쪽 값을 비교해 추세가 꺾이는지 비교 + if nums[mid] > nums[right]: + # 추세 안 꺾임, 시작점을 중간값 인덱스 바로 다음으로 옮긴다 + left = mid + 1 + else: + # 추세 꺾임, 종료지점을 중간값 인덱스로 옮겨준다 + right = mid + + return nums[left] diff --git a/maximum-depth-of-binary-tree/okyungjin.py b/maximum-depth-of-binary-tree/okyungjin.py new file mode 100644 index 0000000000..98a1a00f39 --- /dev/null +++ b/maximum-depth-of-binary-tree/okyungjin.py @@ -0,0 +1,28 @@ +# 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), 최악의 경우 모든 노드가 큐에 담길 수 있음 +class Solution: + def maxDepth(self, root: Optional[TreeNode]) -> int: + 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) + + if node.left: + stack.append((node.left, cur_depth + 1)) + + if node.right: + stack.append((node.right, cur_depth + 1)) + + return max_depth diff --git a/merge-two-sorted-lists/okyungjin.py b/merge-two-sorted-lists/okyungjin.py new file mode 100644 index 0000000000..b1a704b56a --- /dev/null +++ b/merge-two-sorted-lists/okyungjin.py @@ -0,0 +1,110 @@ +# 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: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + # 맨 앞에 더미 노드를 하나 추가한다 + # 없는 상태로 짰는데 if node: else: 구문 늘어나서 맨 앞에 가상의 노드를 하나 추가해줬다 + dummy = ListNode(None) + node = dummy + + while True: + # list1이 비어있으면 현재 노드 뒤로 list2를 이어준다. + if not list1: + node.next = list2 + break + + # list2가 비어있으면 현재 노드 뒤로 list1을 이어준다. + if not list2: + node.next = list1 + break + + # 값을 비교해서 작은 값을 next 노드에 추가한다. + if list1.val <= list2.val: + node.next = ListNode(list1.val) + # list1 하나 소비 + list1 = list1.next + else: + node.next = ListNode(list2.val) + # 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 + + 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 + + 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) diff --git a/word-search/okyungjin.py b/word-search/okyungjin.py new file mode 100644 index 0000000000..84989c32aa --- /dev/null +++ b/word-search/okyungjin.py @@ -0,0 +1,100 @@ +# board: 영문 대/소문자 배열, m * n +# board의 상하좌우로 인접한 문자열을 이어붙여서 word를 완성할 수 있는지 여부를 반환한다. +# 단, 셀은 한번만 사용 가능하다 + +# dfs를 통해 백트랙킹으로 정답을 찾도록 구현했다. + +# L: word 의 길이 +# 시간 복잡도: O(m * n * 4^L) +# 공간 복잡도: O(m * n + L) +class SolutionA: + 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: + 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 + + + # 시작점을 찾는다 + 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: + if str_idx == len(word): + return True + + # 종료조건1: 보드의 좌표를 넘어가는 경우 + if row < 0 or row >= row_size or col < 0 or col >= col_size: + return False + + # 종료조건 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)) + + # 원본 문자열로 복원 + board[row][col] = origin_char + + return found + + + # 시작점을 찾는다 + 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