diff --git a/coin-change/taurus09318976.py b/coin-change/taurus09318976.py new file mode 100644 index 000000000..4008954ee --- /dev/null +++ b/coin-change/taurus09318976.py @@ -0,0 +1,46 @@ +''' +이 문제는 동적 프로그래밍을 이용한 방식이 +1) 모든 가능한 경우를 체계적으로 계산할 수 있고, +2) 중복 계산을 피할 수 있고, +3) 최적의 값을 보장한다는 점에서 좋은 풀이방식임. +다만, +1) amount가 클 경우 메모리 사용이 많을 수 있고, +2) 동전의 가치가 매우 작을 경우 시간이 오래 걸릴 수 있다는 점에서 단점이 있음 +''' + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + # 초기화 + # dp 배열: 각 금액을 만들기 위한 최소 동전 수를 저장 + # amount + 1로 초기화 (불가능한 경우를 구분하기 위해) + # 0원을 만들기 위한 동전 수는 0 + dp = [amount + 1] * (amount + 1) + dp[0] = 0 + + # 동적 프로그래밍 실행: + # 1원부터 amount원까지 순차적으로 계산 + for i in range(1, amount + 1): + # 각 동전에 대해 시도 + for coin in coins: + # 현재 금액이 동전보다 크거나 같은 경우에만 시도 + if i >= coin: + # 현재 동전을 사용하는 경우와 사용하지 않는 경우 중 최소값 선택 + dp[i] = min(dp[i], dp[i - coin] + 1) + + # 결과 반환 + # amount원을 만들 수 없는 경우 -1 반환 + return dp[amount] if dp[amount] != amount + 1 else -1 + + # 시간 복잡도 (Time Complexity): O(n * m) + #n: amount + #m: coins의 길이 + #이유: + #amount까지의 모든 금액에 대해 계산 + #각 금액마다 모든 동전을 확인 + #따라서 시간 복잡도는 O(n * m) + # 공간 복잡도 (Space Complexity): O(n) + # n: amount + #이유: + # dp 배열의 크기가 amount + 1 + # 추가적인 메모리 사용 없음 + # 따라서 공간 복잡도는 O(n) diff --git a/find-minimum-in-rotated-sorted-array/taurus09318976.py b/find-minimum-in-rotated-sorted-array/taurus09318976.py new file mode 100644 index 000000000..8438ddba0 --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/taurus09318976.py @@ -0,0 +1,54 @@ +class Solution: + #findMin 메서드는 정수 리스트를 입력 받아 정수를 반환 + def findMin(self, nums: List[int]) -> int: + # 배열의 시작과 끝 인덱스 설정 + ## left는 배열의 시작 인덱스, right는 배열의 끝 인덱스. len(nums) - 1 + left, right = 0, len(nums) - 1 + + #완전히 정렬되어 배열이 회전되지 않은 경우, 첫 번째 요소가 최소값 + if nums[left] < nums[right]: + return nums[left] + + # 이진 탐색 실행 : left < right인 동안 반복되며, 검색 범위가 1개 이상의 요소를 포함할 때까지 계속됨 + while left < right: + # 중간 인덱스 계산 : 중간값과 오른쪽 끝값 비교 후, 비교 결과에 따라 검색 범위 조정 + mid = (left + right) // 2 + + # 중간값이 오른쪽 값보다 큰 경우 + # -> 최소값은 중간값 오른쪽에 있음 + if nums[mid] > nums[right]: + left = mid + 1 + # 중간값이 오른쪽 값보다 작거나 같은 경우 + # -> 최소값은 중간값 포함 왼쪽에 있음 + else: + right = mid + + # 최종적으로 찾은 최소값 반환 + return nums[left] + +# 테스트 코드 +print(Solution().findMin([3, 4, 5, 1, 2])) # 출력: 1 +print(Solution().findMin([4, 5, 6, 7, 0, 1, 2])) # 출력: 0 +print(Solution().findMin([1])) # 출력: 1 + + + #시간 복잡도 (Time Complexity): O(log n) + #이유: + #이진 탐색 알고리즘 사용 + #각 단계마다 검색 범위가 절반으로 줄어듦 + #n개의 요소를 log₂n 번의 비교로 검색 + #예시: + #n = 8: 최대 3번의 비교 (log₂8 = 3) + #n = 16: 최대 4번의 비교 (log₂16 = 4) + #따라서 시간 복잡도는 O(log n) + #공간 복잡도 (Space Complexity): O(1) + #이유: + #추가적인 데이터 구조 사용하지 않음 + #사용하는 변수: + #left, right, mid: 상수 개수의 정수 변수 + #입력 크기와 관계없이 일정한 메모리만 사용 + #따라서 공간 복잡도는 O(1) + + + + diff --git a/maximum-depth-of-binary-tree/taurus09318976.py b/maximum-depth-of-binary-tree/taurus09318976.py new file mode 100644 index 000000000..84e55cec5 --- /dev/null +++ b/maximum-depth-of-binary-tree/taurus09318976.py @@ -0,0 +1,54 @@ +# 이진 트리의 노드를 정의하는 클래스 +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: TreeNode) -> int: + # 초기 조건 확인. 트리가 비어 있는 경우 깊이는 0 + if not root: + return 0 + + # 큐에 루트 노드를 추가, 깊이를 0으로 초기화 + queue = [root] + depth = 0 + + # 너비우선 탐색(BFS) 실행. 큐가 비어있지 않은 동안 반복 + while queue: + # 새로운 레벨 시작될 때마다 깊이 증가 + depth += 1 + # 현재 레벨의 노드 수 저장 + level_size = len(queue) + + # 현재 레벨의 모든 노드를 순차적으로 처리 + for _ in range(level_size): + # 큐에서 노드 하나 꺼내기 + node = queue.pop(0) + + # 자식 노드 처리. 다음 레벨의 노드들이 됨 + ## 왼쪽 자식이 있으면 큐에 추가 + if node.left: + queue.append(node.left) + ## 오른쪽 자식이 있으면 큐에 추가 + if node.right: + queue.append(node.right) + + # 최종 깊이 반환 + return depth + + #시간 복잡도 (Time Complexity): O(n) + #n: 트리의 총 노드 수 + #이유: + #각 노드를 정확히 한 번씩만 방문 + #각 노드에서의 연산(추가, 제거)은 상수 시간 + #따라서 전체 시간 복잡도는 O(n) + #공간 복잡도 (Space Complexity): O(n) + #최악의 경우: O(n) + #평균적인 경우: O(n/2) ≈ O(n) + #이유: + #큐에 저장되는 최대 노드 수는 트리의 최대 너비 + #완전 이진 트리의 경우 마지막 레벨에 약 n/2개의 노드가 있을 수 있음 + #따라서 공간 복잡도는 O(n) diff --git a/merge-two-sorted-lists/taurus09318976.py b/merge-two-sorted-lists/taurus09318976.py new file mode 100644 index 000000000..7e0723e2f --- /dev/null +++ b/merge-two-sorted-lists/taurus09318976.py @@ -0,0 +1,64 @@ +# 연결 리스트의 노드를 정의하는 클래스 +class ListNode: + def __init__(self, val=0, next=None): + # 노드의 값 + self.val = val + # 다음 노드를 가리키는 포인터 + self.next = next + + +class Solution: + def mergeTwoLists(self, list1: ListNode, list2: ListNode): + # 더미 노드 생성 - 결과 리스트의 시작점으로 사용 + dummy = ListNode() + # 현재 위치를 추적하는 포인터 + current = dummy + + # 병합과정 : 두 리스트가 모두 존재하는 동안 반복, + # 작은 값을 가진 노드를 선택해 결과 리스트에 추가, + # 선택된 리스트의 포인터를 다음 노드로 이동 + # 결과 리스트의 포인터도 다음으로 이동 + while list1 and list2: + # list 1의 값이 list2의 값보다 작거나 같은 경우 + if list1.val <= list2.val: + # list1의 노드를 결과에 추가 + current.next = list1 + #list1의 포인터를 다음 노드로 이동 + list1 = list1.next + else: + #list2의 노드를 결과에 추가 + current.next = list2 + #list2의 포인터를 다음 노드로 이동 + list2 = list2.next + #결과 리스트의 포인터를 다음으로 이동 + current = current.next + + # 한 리스트가 끝나면 다른 리스트의 남은 노드들을 모두 연결 + ## list1에 남은 노드가 있으면 모두 연결 + if list1: + current.next = list1 + + ## list2에 남은 노드가 있으면 모두 연결 + else: + current.next = list2 + + # 더미 노드의 다음 노드부터가 실제 결과 + return dummy.next + + +#시간 복잡도 (Time Complexity): O(n + m) + #n: list1의 길이 + #m: list2의 길이 + #이유: + #각 리스트의 모든 노드를 정확히 한 번씩만 방문 + #각 노드에서의 연산(비교, 연결)은 상수 시간 + #따라서 전체 시간 복잡도는 O(n + m) +#공간 복잡도 (Space Complexity): O(1) + #이유: + #추가적인 데이터 구조를 사용하지 않음 + #사용하는 변수: + #dummy: 상수 공간 + #current: 상수 공간 + #입력 크기와 관계없이 일정한 메모리만 사용 + #따라서 공간 복잡도는 O(1) + diff --git a/word-search/taurus09318976.py b/word-search/taurus09318976.py new file mode 100644 index 000000000..801e5c3d2 --- /dev/null +++ b/word-search/taurus09318976.py @@ -0,0 +1,76 @@ +# 백트래킹 방법을 사용하여 풀이함 +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + # 보드의 행과 열 수 + rows, cols = len(board), len(board[0]) + # 방문한 셀을 추적하는 집합 + visited = set() + + # 깊이우선 탐색(DFS) 함수 정의 + #r, c은 현재 셀의 행과 열, i는 현재 찾고 있는 단어의 인덱스 + def dfs(r, c, i): + #기본 조건 확인 + # 단어의 모든 문자를 찾은 경우 성공 + if i == len(word): + return True + + #유효성 감사 + # 범위를 벗어나거나 이미 방문했거나 현재 셀의 문자가 단어의 현재 문자와 일치하지 않는 경우 + # 범위 검사 + if r < 0 or r >= rows or c < 0 or c >= cols: + return False + + # 방문 및 문자 일치 검사 + if (r, c) in visited or board[r][c] != word[i]: + return False + + # 현재 셀을 방문했다고 표시 + visited.add((r, c)) + + # 상하좌우 방향으로 탐색 + result = (dfs(r + 1, c, i + 1) or # 아래 + dfs(r - 1, c, i + 1) or # 위 + dfs(r, c + 1, i + 1) or # 오른쪽 + dfs(r, c - 1, i + 1)) # 왼쪽 + + # 백트래킹: 현재 셀을 방문하지 않은 것으로 표시 + #현재 경로가 실패했으므로 방문 표시 제거 + visited.remove((r, c)) + + return result + + # 보드의 모든 셀에서 시작점으로 시도 + for r in range(rows): + for c in range(cols): + if dfs(r, c, 0): + return True + + return False + + #시간 복잡도 (Time Complexity): O(m * n * 4^L) + #m: 보드의 행 수 + #n: 보드의 열 수 + #L: 단어의 길이 + #이유: + #각 셀에서 시작할 수 있음: O(m * n) + #각 위치에서 4방향으로 탐색 가능: O(4^L) + #최악의 경우 모든 경로를 탐색해야 함 + #따라서 시간 복잡도는 O(m * n * 4^L) + #공간 복잡도 (Space Complexity): O(L) + #L: 단어의 길이 + #이유: + #재귀 호출 스택의 깊이는 단어의 길이에 비례 + #visited 집합의 크기도 단어의 길이에 비례 + #따라서 공간 복잡도는 O(L) + + #DFS vs BFS 비교: + #DFS (깊이 우선 탐색): + #한 경로를 끝까지 탐색 + #스택/재귀 사용 + #메모리 사용이 적음 + #최단 경로 보장하지 않음 + #BFS (너비 우선 탐색): + #같은 레벨의 모든 노드를 먼저 탐색 + #큐 사용 + #메모리 사용이 많음 + #최단 경로 보장