Repository navigation
[alphaorderly] WEEK 14 Solutions #2868
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
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # 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) | ||
| - n: 트리의 모든 노드 개수 (각 노드를 한 번씩 방문) | ||
| 공간복잡도: O(n) | ||
| - n: 최악의 경우 큐(queue)에 저장되는 노드 수(가장 마지막 레벨의 노드 수와 동일) | ||
|
|
||
| BFS(너비 우선 탐색)을 활용하여 트리를 레벨별로 순회하며, | ||
| 각 레벨마다 노드의 값을 리스트로 모아 결과 리스트에 추가합니다. | ||
| """ | ||
| class Solution: | ||
| def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: | ||
|
|
||
| if not root: | ||
| return [] | ||
|
|
||
| queue = deque([root]) | ||
| ans = [] | ||
|
|
||
| while queue: | ||
| temp = [] | ||
| Q = len(queue) | ||
| for _ in range(Q): | ||
| node = queue.popleft() | ||
| temp.append(node.val) | ||
|
|
||
| if node.left: | ||
| queue.append(node.left) | ||
| if node.right: | ||
| queue.append(node.right) | ||
| ans.append(temp) | ||
|
|
||
| return ans |
|
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. 🏷️ 알고리즘 패턴 분석counting-bits/alphaorderly.py"""
시간복잡도: O(n)
- n: 입력된 숫자 n까지의 범위
공간복잡도: O(n)
- n: 결과값 리스트(ans)의 크기
"""
class Solution:
def countBits(self, n: int) -> List[int]:
ans = [0] * (n + 1)
for i in range(1, n + 1):
ans[i] = (i & 1) + ans[i // 2]
return ans
📊 시간/공간 복잡도 분석
피드백: 배열 인덱스 i에 대해 i의 하위 구조를 활용해 앞선 값을 재활용하므로 한 번의 순회로 결과를 얻습니다. 개선 제안: 현재 구현이 적절해 보입니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| """ | ||
| 시간복잡도: O(n) | ||
| - n: 입력된 숫자 n까지의 범위 | ||
| 공간복잡도: O(n) | ||
| - n: 결과값 리스트(ans)의 크기 | ||
| """ | ||
| class Solution: | ||
| def countBits(self, n: int) -> List[int]: | ||
| ans = [0] * (n + 1) | ||
|
|
||
| for i in range(1, n + 1): | ||
| ans[i] = (i & 1) + ans[i // 2] | ||
|
|
||
| return ans |
|
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. 🏷️ 알고리즘 패턴 분석house-robber-ii/alphaorderly.py"""
시간복잡도: O(n)
- n: 입력된 배열 nums의 길이
공간복잡도: O(n)
- n: DP 리스트(first, second)의 크기
집이 원형이라 첫 집과 마지막 집을 동시에 털 수 없으므로,
두 구간으로 나누어 각각 선형 House Robber를 적용합니다.
first는 마지막 집을 제외한 구간(0 ~ n-2),
second는 첫 집을 제외한 구간(1 ~ n-1)의 최대 금액을 저장합니다.
두 구간의 마지막 값 중 더 큰 값을 반환합니다.
"""
class Solution:
def rob(self, nums: List[int]) -> int:
if len(nums) <= 2:
return max(nums)
first = [nums[0], max(nums[0], nums[1])]
second = [0, nums[1]]
for i in range(2, len(nums)):
if i != len(nums) - 1:
first.append(max(nums[i] + first[i - 2], first[i - 1]))
second.append(max(nums[i] + second[i - 2], second[i - 1]))
return max(first[-1], second[-1])
📊 시간/공간 복잡도 분석
피드백: 두 구간 각각 독립적으로 최적해를 구하므로 원형 제약을 회피하며 DP로 해결합니다. 개선 제안: 특정 예외 처리로 간결하게 개선될 수는 있지만 현재 구현도 적절합니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| """ | ||
| 시간복잡도: O(n) | ||
| - n: 입력된 배열 nums의 길이 | ||
| 공간복잡도: O(n) | ||
| - n: DP 리스트(first, second)의 크기 | ||
|
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. 점화식을 보면 길이가 n인 리스트 대신에 변수 2개만 있어도 충분할 것 같네요? 메모리 최적화의 기회가 보입니다. |
||
|
|
||
| 집이 원형이라 첫 집과 마지막 집을 동시에 털 수 없으므로, | ||
| 두 구간으로 나누어 각각 선형 House Robber를 적용합니다. | ||
|
|
||
| first는 마지막 집을 제외한 구간(0 ~ n-2), | ||
| second는 첫 집을 제외한 구간(1 ~ n-1)의 최대 금액을 저장합니다. | ||
|
|
||
| 두 구간의 마지막 값 중 더 큰 값을 반환합니다. | ||
| """ | ||
| class Solution: | ||
| def rob(self, nums: List[int]) -> int: | ||
|
|
||
| if len(nums) <= 2: | ||
| return max(nums) | ||
|
|
||
| first = [nums[0], max(nums[0], nums[1])] | ||
| second = [0, nums[1]] | ||
|
|
||
| for i in range(2, len(nums)): | ||
| if i != len(nums) - 1: | ||
| first.append(max(nums[i] + first[i - 2], first[i - 1])) | ||
| second.append(max(nums[i] + second[i - 2], second[i - 1])) | ||
|
Comment on lines
+24
to
+27
Collaborator
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. 하나의 루프에서 두 가지 동작을 하네요. prev2, prev1 변수를 사용하는 하나의 헬퍼 함수를 두면 공간 복잡도가 O(1)로 줄고 가독성도 개선할 수 있을 거 같습니다. |
||
|
|
||
| return max(first[-1], second[-1]) | ||
|
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. 🏷️ 알고리즘 패턴 분석meeting-rooms-ii/alphaorderly.py"""
시간복잡도: O(n log n)
- n: 입력된 배열 intervals의 길이
공간복잡도: O(n)
- n: 진행 중인 회의의 종료 시각을 저장하는 최소 힙의 크기
intervals를 시작 시각 기준으로 정렬합니다.
최소 힙에는 아직 사용 중인 회의실의 종료 시각을 저장합니다.
각 회의의 시작 시각이 힙의 가장 이른 종료 시각 이후이면
그 회의실은 비었으므로 힙에서 제거하고,
현재 회의의 종료 시각을 힙에 넣습니다.
힙 크기의 최댓값이 필요한 최소 회의실 개수입니다.
"""
class Solution:
def minMeetingRooms(self, intervals: List[List[int]]) -> int:
intervals.sort()
ans = 1
heap = []
for start, end in intervals:
while heap and heap[0] <= start:
heappop(heap)
heappush(heap, end)
ans = max(ans, len(heap))
return ans
📊 시간/공간 복잡도 분석
피드백: 정렬과 힙을 활용해 겹치는 구간을 효율적으로 추적하며 최댓 힙 크기가 필요 회의실 수를 의미합니다. 개선 제안: 현 구현이 일반적으로 충분합니다. 입력 크기가 커지면 힙 연산의 상수층을 고려한 최적화 가능성은 여전히 남아 있습니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| """ | ||
| 시간복잡도: O(n log n) | ||
| - n: 입력된 배열 intervals의 길이 | ||
| 공간복잡도: O(n) | ||
| - n: 진행 중인 회의의 종료 시각을 저장하는 최소 힙의 크기 | ||
|
|
||
| intervals를 시작 시각 기준으로 정렬합니다. | ||
| 최소 힙에는 아직 사용 중인 회의실의 종료 시각을 저장합니다. | ||
|
|
||
| 각 회의의 시작 시각이 힙의 가장 이른 종료 시각 이후이면 | ||
| 그 회의실은 비었으므로 힙에서 제거하고, | ||
| 현재 회의의 종료 시각을 힙에 넣습니다. | ||
| 힙 크기의 최댓값이 필요한 최소 회의실 개수입니다. | ||
| """ | ||
| class Solution: | ||
| def minMeetingRooms(self, intervals: List[List[int]]) -> int: | ||
| intervals.sort() | ||
| ans = 1 | ||
| heap = [] | ||
|
|
||
| for start, end in intervals: | ||
| while heap and heap[0] <= start: | ||
| heappop(heap) | ||
| heappush(heap, end) | ||
| ans = max(ans, len(heap)) | ||
|
|
||
| return ans |
|
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. 🏷️ 알고리즘 패턴 분석word-search-ii/alphaorderly.py"""
시간복잡도: O(w + m * n * 4^l)
- w: 모든 단어 길이의 합 (트라이 구성)
- m, n: 보드의 행, 열
- l: 단어의 최대 길이 (각 칸에서 상하좌우 DFS)
공간복잡도: O(w + l)
- w: 트라이와 정답 집합
- l: DFS 재귀 깊이와 현재 경로의 방문 집합
단어를 트라이에 넣은 뒤, 보드의 각 칸에서 DFS로 이어지는 단어를 찾습니다.
이미 찾은 단어는 is_end를 끄고, 더 이상 이어지지 않는 노드는 트라이에서 지워
같은 접두사를 다시 탐색하지 않습니다.
"""
class TrieNode:
def __init__(self):
self.is_end = False
self.children = dict()
def add_word(self, word: str):
node = self
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
# 이 글자에서 단어가 끝난다
node.is_end = True
class Solution:
def findWords(self, board: list[list[str]], words: list[str]) -> list[str]:
ROW, COL = len(board), len(board[0])
# 상하좌우
DIR = [[1, 0], [0, 1], [-1, 0], [0, -1]]
trie = TrieNode()
for word in words:
trie.add_word(word)
def bound(row: int, col: int) -> bool:
return 0 <= row < ROW and 0 <= col < COL
# ans: 찾은 단어, visited: 현재 경로에서 지난 칸
ans, visited = set(), set()
def search(r: int, c: int, node: TrieNode, word: str):
# 다음 글자가 트라이에 없거나, 같은 칸을 다시 밟으면 중단
if board[r][c] not in node.children or (r, c) in visited:
return
visited.add((r, c))
# 가지치기할 때 부모에서 현재 글자를 지우기 위해 보관
parent = node
ch = board[r][c]
node = node.children[ch]
word += ch
if node.is_end:
ans.add(word)
# 같은 단어는 한 번만 찾는다
node.is_end = False
for dr, dc in DIR:
tr, tc = r + dr, c + dc
if not bound(tr, tc):
continue
search(tr, tc, node, word)
# 백트래킹: 이 칸을 다른 경로에서 다시 쓸 수 있게 되돌린다
visited.remove((r, c))
# 자식도 없고 여기서 끝나는 단어도 없으면 트라이에서 제거
if not node.children and not node.is_end:
del parent.children[ch]
for r in range(ROW):
for c in range(COL):
search(r, c, trie, "")
return list(ans)
📊 시간/공간 복잡도 분석
피드백: 사전 구성된 트라이와 가지치기로 중복 탐색을 줄이며 DFS를 통해 보드 전체를 탐색합니다. 개선 제안: 현재 아이디어가 효과적이지만, 트라이 제거 및 재탐색 비용을 더 줄일 수 있는 추가 최적화 여지가 있습니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| """ | ||
| 시간복잡도: O(w + m * n * 4^l) | ||
| - w: 모든 단어 길이의 합 (트라이 구성) | ||
| - m, n: 보드의 행, 열 | ||
| - l: 단어의 최대 길이 (각 칸에서 상하좌우 DFS) | ||
| 공간복잡도: O(w + l) | ||
| - w: 트라이와 정답 집합 | ||
| - l: DFS 재귀 깊이와 현재 경로의 방문 집합 | ||
|
|
||
| 단어를 트라이에 넣은 뒤, 보드의 각 칸에서 DFS로 이어지는 단어를 찾습니다. | ||
| 이미 찾은 단어는 is_end를 끄고, 더 이상 이어지지 않는 노드는 트라이에서 지워 | ||
| 같은 접두사를 다시 탐색하지 않습니다. | ||
| """ | ||
| class TrieNode: | ||
| def __init__(self): | ||
| self.is_end = False | ||
| self.children = dict() | ||
|
|
||
| def add_word(self, word: str): | ||
| node = self | ||
|
|
||
| for ch in word: | ||
| if ch not in node.children: | ||
| node.children[ch] = TrieNode() | ||
| node = node.children[ch] | ||
|
|
||
| # 이 글자에서 단어가 끝난다 | ||
| node.is_end = True | ||
|
|
||
|
|
||
| class Solution: | ||
| def findWords(self, board: list[list[str]], words: list[str]) -> list[str]: | ||
| ROW, COL = len(board), len(board[0]) | ||
| # 상하좌우 | ||
| DIR = [[1, 0], [0, 1], [-1, 0], [0, -1]] | ||
|
|
||
| trie = TrieNode() | ||
| for word in words: | ||
| trie.add_word(word) | ||
|
|
||
| def bound(row: int, col: int) -> bool: | ||
| return 0 <= row < ROW and 0 <= col < COL | ||
|
|
||
| # ans: 찾은 단어, visited: 현재 경로에서 지난 칸 | ||
| ans, visited = set(), set() | ||
|
|
||
| def search(r: int, c: int, node: TrieNode, word: str): | ||
| # 다음 글자가 트라이에 없거나, 같은 칸을 다시 밟으면 중단 | ||
| if board[r][c] not in node.children or (r, c) in visited: | ||
| return | ||
|
|
||
| visited.add((r, c)) | ||
|
|
||
| # 가지치기할 때 부모에서 현재 글자를 지우기 위해 보관 | ||
| parent = node | ||
| ch = board[r][c] | ||
|
|
||
| node = node.children[ch] | ||
| word += ch | ||
|
|
||
| if node.is_end: | ||
| ans.add(word) | ||
| # 같은 단어는 한 번만 찾는다 | ||
| node.is_end = False | ||
|
|
||
| for dr, dc in DIR: | ||
| tr, tc = r + dr, c + dc | ||
| if not bound(tr, tc): | ||
| continue | ||
| search(tr, tc, node, word) | ||
|
|
||
| # 백트래킹: 이 칸을 다른 경로에서 다시 쓸 수 있게 되돌린다 | ||
| visited.remove((r, c)) | ||
|
|
||
| # 자식도 없고 여기서 끝나는 단어도 없으면 트라이에서 제거 | ||
| if not node.children and not node.is_end: | ||
| del parent.children[ch] | ||
|
|
||
| for r in range(ROW): | ||
| for c in range(COL): | ||
| search(r, c, trie, "") | ||
|
|
||
| return list(ans) | ||
|
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. 굳이 ans에 세트를 할당하고 마지막에 다시 리스트로 바꿀 이유가 있을까요? 같은 단어가 다시 추가될 수 있는 경우가 있는지 생각해보시면 좋을 것 같습니다. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏷️ 알고리즘 패턴 분석
binary-tree-level-order-traversal/alphaorderly.py
📊 시간/공간 복잡도 분석
피드백: 모든 노드를 한 번 방문하고 큐에 최대 한 레벨의 노드들이 저장되므로 시간은 트리의 노드 수에 비례하고, 공간은 큐의 최대 크기와 결과 저장으로 결정됩니다.
개선 제안: 현재 구현은 일반적이고 안정적입니다. 트리의 깊이가 매우 커진 경우 큐의 크기가 커지므로 필요 시 레벨 구분 없이 인덱스 기반 순회로 최적화 고려 가능.