-
-
Notifications
You must be signed in to change notification settings - Fork 361
[okyungjin] WEEK 04 Solutions #2743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
276ce10
f245112
ea4170f
441ac50
d77d59d
9afa0f2
e65811c
b5c8317
6808c86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 금액별로 모든 동전을 시도해 최소값을 갱신하는 DP 풀이로 문제의 조건에 맞는 최단 동전 수를 구한다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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] |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 정렬된 배열이 회전되었을 때 중간값과 끝 값을 비교해 탐색 범위를 절단한다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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] |
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DaleStudy 내가 의도한 유형으로 안 풀었어?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 의도한 유형에 맞춰 풀었는지 확인해 드릴게요.
정리: 의도한 유형(표준 솔루션 + 로그/선언적 복잡도 표기)에 대체로 부합합니다. 테스트 예제나 간단한 검증 코드가 있으면 더 확실해질 거예요.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 스택을 사용해 각 노드의 깊이를 관리하여 전체 트리를 탐색한다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
|
|
||
|
Comment on lines
+15
to
+21
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 구현은 LIFO 방식으로 동작하므로, 변수 이름도 반대로 FIFO 방식의 큐로 구현한다면 노드가 깊이 순서대로 처리되므로,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ICE0208 놓친 부분 짚어주셔서 감사합니다!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. stack으로 변수명 수정했습니다(6808c86) 감사합니다! |
||
| if node.left: | ||
| stack.append((node.left, cur_depth + 1)) | ||
|
|
||
| if node.right: | ||
| stack.append((node.right, cur_depth + 1)) | ||
|
|
||
| return max_depth | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DaleStudy 개선 제안: 더 나은 변수 네이밍과 주석 정리로 가독성을 높일 수 있습니다. 는 어떤 방식을 말하는건지 제안해주라
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 제안이에요. 더 나은 변수 네이밍과 주석 정리는 코드 가독성과 유지보수에 큰 도움이 됩니다. 아래 방식으로 구체적으로 제안드립니다.
원하는 방향으로 샘플 리팩틴 코드도 같이 드릴게요.
okyungjin marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 둘 다 선형 시간복잡도로 병합하며, 메모리 사용은 노드 재사용 여부에 따라 달라진다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분에서 ListNode(list1.val)로 매번 새 노드를 생성하시는데, 기존 노드를 그대로 연결(node.next = list1)하면 새 노드를 안 만들어도 돼서 공간 복잡도를 O(1)로 줄일 수 있을 것 같아요. 저는 그렇게 풀었는데 참고하시라고 남깁니다! 전체 로직은 깔끔하게 잘 읽혔어요 👍
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chapse57 코멘트 감사합니다! 풀이를 2가지로 작성했는데 이 부분 언급해주신게 맞을까요? |
||
| # 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) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DaleStudy 시간 복잡도랑 공간 복잡도 어떻게 계산했어? 과정 알려줘
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 다음 코드들에서 대표적으로 다루는 두 문제의 시간/공간 복잡도 계산 방법을 정리해 드립니다.
추가 팁
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dalstudy word-search 프루닝 어떻게 하면 더 좋을지 제안해주라. 해시맵으로 문자열의 좌표를 미리 기록해두기?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DFS를 시작하기 전에 단어의 길이가 보드 전체 칸 수보다 긴지 확인하면, 경로를 만들 수 없는 입력을 탐색 전에 바로 제외할 수 있을 것 같습니다!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 의견 감사합니다!
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: SolutionA는 방문 배열을 사용하고, Solution은 보드를 직접 마킹해 공간을 절약한다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
Uh oh!
There was an error while loading. Please reload this page.