[alphaorderly] WEEK 09 Solutions - #2822
Conversation
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
linked-list-cycle/alphaorderly.py
"""
시간복잡도: O(n)
공간복잡도: O(1)
1. 토끼와 거북이를 초기화한다.
- 토끼 : 두 칸씩 이동
- 거북이 : 한 칸씩 이동
2. 토끼와 거북이가 만날 때까지 이동한다.
3. 토끼와 거북이가 만나면 사이클이 있다고 판단한다.
4. 토끼와 거북이가 만나지 않으면 사이클이 없다고 판단한다.
# 원리 #
- 사이클이 존재하지 않는다면 토끼와 거북이는 결국 끝에 도달한다.
- 사이클이 존재한다면 토끼와 거북이는 결국 사이클 내에서 만난다.
"""
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
hare = head
tortoise = head
while hare and hare.next:
hare = hare.next.next
tortoise = tortoise.next
if hare is tortoise:
return True
return False- 패턴: Two Pointers, Fast & Slow Pointers
- 설명: 해당 코드는 토끼-거북이 알고리즘으로, 같은 포인터 두 개를 서로 다른 속도로 움직여 순환 여부를 판단한다. 이를 통해 한 번에 하나의 노드를 건너가며 순환 여부를 O(n) 시간, O(1) 공간으로 탐지한다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(1) |
피드백: 토끼와 거북이 알고리즘으로 순차 탐색 중 사이클 여부를 빠르게 판단한다. 추가 공간은 상수로 유지된다.
개선 제안: 현재 구현은 의도한 대로 동작하지만, 입력 head가 None인 경우를 명시적으로 처리하는 방어 코드가 있으면 더 robust합니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
📊 alphaorderly 님의 학습 현황이번 주 제출 문제
누적 학습 요약
문제 풀이 현황
🤖 이 댓글은 GitHub App을 통해 자동으로 작성되었습니다. 🔢 API 사용량 (gpt-5-nano)
|
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
maximum-product-subarray/alphaorderly.py
"""
시간복잡도: O(n)
공간복잡도: O(1)
- 만약 현재 숫자가 음수라면, 양수 최대값(pos)과 음수 최소값(neg)을 서로 교환한다.
- 이유: 음수 * 음수 = 양수이므로, 음수를 만나면 최대값/최소값 후보가 바뀔 수 있다.
- pos와 neg를 nums의 첫 번째 값으로 초기화한다.
- pos: 현재까지의 양수 곱셈 최대값
- neg: 현재까지의 음수 곱셈 최소값
- ans를 nums의 첫 번째 값으로 초기화한다.
- ans: 현재까지의 최대 곱셈 결과
- nums의 두 번째 원소부터 끝까지 pos와 neg를 갱신한다.
- pos는 현재 숫자와 pos*현재 숫자 중 큰 값
- neg는 현재 숫자와 neg*현재 숫자 중 작은 값
- ans를 pos와 비교하여 최대값으로 갱신한다.
- 반복이 끝나면 ans를 반환한다.
"""
class Solution:
def maxProduct(self, nums: List[int]) -> int:
pos = neg = ans = nums[0]
N = len(nums)
for i in range(1, N):
if nums[i] < 0:
neg, pos = pos, neg
pos = max(nums[i], nums[i] * pos)
neg = min(nums[i], nums[i] * neg)
ans = max(ans, pos)
return ans- 패턴: Dynamic Programming, Two Pointers, Greedy
- 설명: 연속 부분배열의 최대 곱을 찾기 위해 양수/음수의 곱의 특성을 이용하는 단일 패스 해법으로, 현재 위치의 값과 이전까지의 최댓값/최솟값을 갱신하는 DP 성격의 상태 전개와 이를 이용한 그리디적 선택이 결합되어 있습니다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(1) |
피드백: pos와 neg를 각각 현재까지의 양수/음수 구간의 최대/최소 곱으로 관리한다. 음수의 등장 시 교환이 핵심이다.
개선 제안: 특정 입력에 대해 nums가 비어있을 수 있는 경우에 대비한 입력 검증을 추가하면 안정성이 올라갑니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
minimum-window-substring/alphaorderly.py
"""
시간복잡도: O(n)
공간복잡도: O(1)
슬라이딩 윈도우 기법을 사용해 문자열 s에서 t의 모든 문자를 포함하는 최소 윈도우를 찾는다.
Window 클래스는 타겟 문자열(t)에 구성된 각 문자별 필요한 개수를 카운트하고,
윈도우 내 해당 문자 개수를 관리하며, 현재 윈도우가 타겟을 만족하는지(check) 확인한다.
add 메서드는 윈도우에 문자를 추가하여 개수를 갱신하고,
- 타겟의 문자 갯수와 같아지면 key_count를 증가시킨다.
remove 메서드는 윈도우에서 문자를 제거하여 개수를 줄인다.
- 타겟의 문자 갯수보다 작아지면 key_count를 감소시킨다.
check 메서드는 윈도우에 타겟 문자가 필요한 만큼 모두 포함되어 있는지 검사한다.
- key_count와 target_count가 같으면 True를 반환한다.
"""
class Window:
def __init__(self, target: str):
self.target = Counter(target)
self.window = defaultdict(int)
self.key_count = 0
self.target_count = len(self.target)
def check(self):
return self.key_count == self.target_count
def add(self, ch: str):
self.window[ch] += 1
if self.window[ch] == self.target[ch]:
self.key_count += 1
def remove(self, ch: str):
self.window[ch] -= 1
if self.window[ch] < self.target[ch]:
self.key_count -= 1
class Solution:
def minWindow(self, s: str, t: str) -> str:
window = Window(t)
left = 0
ans = (-float("inf"), float("inf"))
for right, value in enumerate(s):
window.add(value)
if not window.check():
continue
while window.check():
window.remove(s[left])
left += 1
if ans[1] - ans[0] > right - left:
ans = (left - 1, right)
if ans[0] == -float("inf"):
return ""
return s[ans[0] : ans[1] + 1]- 패턴: Sliding Window
- 설명: 주어진 코드는 가변 길이 윈도우를 왼쪽/오른쪽 포인터로 조절하며, 타깃 문자열의 모든 문자를 포함하는 최소 윈도우를 찾는 로직으로 구성되어 있습니다. 윈도우 내부 문자를 관리하고 조건 충족 시 윈도우를 축소하는 흐름이 Sliding Window 패턴에 해당합니다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(1) |
피드백: Window 클래스를 통해 필요한 문자 수를 추적하고 키 카운트로 윈도우를 축소한다. 추가 정보 관리로 공간을 상수로 유지한다.
개선 제안: Window 구현에서 Counter/ defaultdict 초기화 비용 고려 시, 정규 딕셔너리 기본값 사용 여부를 재점검해 성능 여지를 확인해볼 수 있습니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
pacific-atlantic-water-flow/alphaorderly.py
"""
시간복잡도: O(m * n)
공간복잡도: O(m * n)
1. 태평양과 대서양 각각에서 물이 도달할 수 있는 위치를 기록할 2차원 배열(check)을 만든다.
2. 태평양과 맞닿은 칸들(왼쪽 열과 위쪽 행)에는 태평양 도달 가능(값 1), 대서양과 맞닿은 칸들(오른쪽 열과 아래쪽 행)에는 대서양 도달 가능(값 2) 표시를 한다.
- 1: 태평양, 2: 대서양 — 2진수로는 각각 01, 10이라는 의미임.
3. 각 바다에 인접한 칸들에서 시작해, BFS를 이용해 도달 가능한 모든 칸을 확장한다.
- 인접한 칸으로 이동할 때, 항상 지금 칸보다 높이가 같거나 더 높은 칸으로만 물이 흐를 수 있다(즉, 물이 거슬러 흐르는 조건).
- 이미 같은 바다에서 방문한 적이 있는 칸은 건너뛴다.
- 새로운 위치에 도달할 때마다 check 배열을 갱신하고 큐에 추가한다.
4. 태평양과 대서양에서 모두 도달 가능한 칸(값이 3이 된 칸)을 답으로 모아 반환한다.
# 비트마스킹 기법 사용 원리 #
- 태평양과 대서양 두 바다에서 도달 가능한 칸을 표시하기 위해 비트마스킹 기법을 사용한다.
- 각 칸에 대해, 1비트(01)는 태평양(Pacific), 2비트(10)는 대서양(Atlantic) 도달 가능을 의미한다.
- 예를 들어 각 칸의 값이 1이면 태평양, 2이면 대서양, 3이면 두 바다 모두에 도달 가능한 칸임을 나타낸다.
- bfs/dfs로 인접 칸(상하좌우)로 확장할 때, 이미 해당 바다의 비트가 켜져 있으면 방문하지 않는다.
"""
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
ROW = len(heights)
COL = len(heights[0])
DIR = [
[1, 0],
[0, 1],
[-1, 0],
[0, -1]
]
check = [[0] * COL for _ in range(ROW)]
for r in range(ROW):
check[r][0] |= 1
check[r][COL - 1] |= 2
for c in range(COL):
check[0][c] |= 1
check[ROW - 1][c] |= 2
queue = deque([])
for r in range(ROW):
for c in range(COL):
if check[r][c] != 0:
queue.append((r, c))
def bound(row: int, col: int) -> bool:
return 0 <= row < ROW and 0 <= col < COL
while queue:
r, c = queue.popleft()
for dr, dc in DIR:
tr, tc = r + dr, c + dc
if not bound(tr, tc):
continue
if heights[tr][tc] < heights[r][c]:
continue
if check[tr][tc] | check[r][c] == check[tr][tc]:
continue
check[tr][tc] |= check[r][c]
queue.append((tr, tc))
ans = []
for r in range(ROW):
for c in range(COL):
if check[r][c] == 3:
ans.append([r, c])
return ans- 패턴: Breadth-First Search, Dynamic Programming, Hash Map / Hash Set
- 설명: 주요 로직이 BFS를 사용해 두 바다로 차례대로 도달 여부를 확장하는 방식이며, 각 칸의 상태를 비트마스킹으로 관리해 두 바다의 도달 여부를 합쳐 결과를 얻는 패턴이 보인다. 또한 상태 갱신과 방문 여부 관리가 DP적 아이디어와 비트마스킹의 활용으로 연결된다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(m * n) |
| Space | O(m * n) |
피드백: 각 칸에 두 바다 도달 여부를 비트로 표시하고, 양 방향으로 BFS를 확장한다. 중복 방문 최소화가 핵심이다.
개선 제안: 메모리 사용을 줄이고자 비트마스킹 대신 두 개의 독립 체크 배열을 사용하는 방식도 고려 가능하다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
sum-of-two-integers/alphaorderly.py
"""
시간복잡도: O(1)
공간복잡도: O(1)
16비트 정수 범위에서 각 비트를 하나씩 확인하며 덧셈을 수행한다.
각 비트의 합은 XOR 연산으로 구하고,
올림수(carry)는 두 비트 이상이 1인 경우를 OR 연산으로 계산한다.
- 전가산기 원리 적용
계산된 각 비트는 ans의 해당 위치에 저장한다.
Python의 int는 고정된 비트 폭을 가지지 않으므로,
최종 결과의 최상위 비트가 1인 경우에는 16비트 음수로 직접 변환한다.
MASK와 XOR하여 16비트 범위 안에서 비트를 반전한 뒤,
~ 연산을 적용하여 Python의 음수 정수 형태로 변환한다.
"""
class Solution:
def getSum(self, a: int, b: int) -> int:
MASK = 0xFFFF
CHECK = 0x8000
ans = carry = left = 0
for i in range(16):
a_bit = a & 1
b_bit = b & 1
left = a_bit ^ b_bit ^ carry
carry = (a_bit & b_bit) | (b_bit & carry) | (a_bit & carry)
ans |= left * 2**i
a >>= 1
b >>= 1
if ans & CHECK == 0:
return ans
else:
return ~(MASK ^ ans)- 패턴: Bit Manipulation
- 설명: 두 정수의 각 비트를 순회하며, 비트 연산으로 덧셈을 구현하고, 캐리와 합을 비트 단위로 계산하는 방식이다. 16비트 고정 폭 가정과 최종 부호 처리로 비트 조작 패턴이 핵심이다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(1) |
| Space | O(1) |
피드백: 비트 연산으로 덧셈의 각 비트를 차례대로 계산하고 최종 음수 표시를 처리한다. 파이썬의 가변 정수 특성을 활용해 경계 처리를 한다.
개선 제안: 입력 수의 범위를 명시적으로 확인하고, 더 큰 비트 수에서도 동작하도록 일반화하는 전략도 고려해볼 만하다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
DaleSeo
left a comment
There was a problem hiding this comment.
수고하셨습니다. 훌륭하게 잘 푸셔서 아주 사소한 질문만 남깁니다.
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
maximum-product-subarray/alphaorderly.py
"""
시간복잡도: O(n)
공간복잡도: O(1)
- 만약 현재 숫자가 음수라면, 양수 최대값(pos)과 음수 최소값(neg)을 서로 교환한다.
- 이유: 음수 * 음수 = 양수이므로, 음수를 만나면 최대값/최소값 후보가 바뀔 수 있다.
- pos와 neg를 nums의 첫 번째 값으로 초기화한다.
- pos: 현재까지의 곱셈 최대값
- neg: 현재까지의 곱셈 최소값
- pos, neg로 이름 붙힌 이유는 nums에 음수가 있을 때 각각 양수와 음수가 될 수 있기 때문이다.
- ans를 nums의 첫 번째 값으로 초기화한다.
- ans: 현재까지의 최대 곱셈 결과
- nums의 두 번째 원소부터 끝까지 pos와 neg를 갱신한다.
- pos는 현재 숫자와 pos*현재 숫자 중 큰 값
- neg는 현재 숫자와 neg*현재 숫자 중 작은 값
- ans를 pos와 비교하여 최대값으로 갱신한다.
- 반복이 끝나면 ans를 반환한다.
"""
class Solution:
def maxProduct(self, nums: List[int]) -> int:
pos = neg = ans = nums[0]
N = len(nums)
for i in range(1, N):
if nums[i] < 0:
neg, pos = pos, neg
pos = max(nums[i], nums[i] * pos)
neg = min(nums[i], nums[i] * neg)
ans = max(ans, pos)
return ans- 패턴: Two Pointers, Dynamic Programming, Greedy
- 설명: 연속 부분 배열의 곱의 최대를 구하는 문제로, 현재 위치에서 양수/음수 상태를 유지하며 최댓값과 최솟값을 함께 추적하는 방식은 DP적 성격과 최적해를 한 번에 갱신하는 Greedy 요소를 포함한다. 포인터가 한 방향으로만 움직이며 상태를 업데이트하는 구조로 해석 가능하다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(1) |
피드백: pos와 neg를 각각 현재까지의 곱의 최고/최저로 유지하며 음수 처리 시 위치를 교환하는 방식으로 전체를 한 번의 순회로 해결한다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
minimum-window-substring/alphaorderly.py
"""
시간복잡도: O(n)
공간복잡도: O(n)
슬라이딩 윈도우 기법을 사용해 문자열 s에서 t의 모든 문자를 포함하는 최소 윈도우를 찾는다.
Window 클래스는 타겟 문자열(t)에 구성된 각 문자별 필요한 개수를 카운트하고,
윈도우 내 해당 문자 개수를 관리하며, 현재 윈도우가 타겟을 만족하는지(check) 확인한다.
add 메서드는 윈도우에 문자를 추가하여 개수를 갱신하고,
- 타겟의 문자 갯수와 같아지면 key_count를 증가시킨다.
remove 메서드는 윈도우에서 문자를 제거하여 개수를 줄인다.
- 타겟의 문자 갯수보다 작아지면 key_count를 감소시킨다.
check 메서드는 윈도우에 타겟 문자가 필요한 만큼 모두 포함되어 있는지 검사한다.
- key_count와 target_count가 같으면 True를 반환한다.
"""
class Window:
def __init__(self, target: str):
self.target = Counter(target)
self.window = defaultdict(int)
self.key_count = 0
self.target_count = len(self.target)
def check(self):
return self.key_count == self.target_count
def add(self, ch: str):
self.window[ch] += 1
if self.window[ch] == self.target[ch]:
self.key_count += 1
def remove(self, ch: str):
self.window[ch] -= 1
if self.window[ch] < self.target[ch]:
self.key_count -= 1
class Solution:
def minWindow(self, s: str, t: str) -> str:
window = Window(t)
left = 0
ans = (-float("inf"), float("inf"))
for right, value in enumerate(s):
window.add(value)
if not window.check():
continue
while window.check():
window.remove(s[left])
left += 1
if ans[1] - ans[0] > right - left:
ans = (left - 1, right)
if ans[0] == -float("inf"):
return ""
return s[ans[0] : ans[1] + 1]- 패턴: Sliding Window
- 설명: 주어진 코드는 타깃 문자열의 모든 문자를 포함하는 최소 윈도우를 찾기 위해 좌우 포인터를 늘리며 윈도우를 관리하는 sliding window 패턴을 사용한다. window 내부 카운팅으로 조건을 확인하고 필요 시 왼쪽 포인터를 이동해 윈도우를 축소한다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(n) |
| Space | O(n) |
피드백: Window 클래스가 타겟 문자 개수와 윈도우 내 개수를 관리하며, check를 통해 필요 조건 만족 여부를 확인한다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
There was a problem hiding this comment.
🏷️ 알고리즘 패턴 분석
sum-of-two-integers/alphaorderly.py
"""
시간복잡도: O(1)
공간복잡도: O(1)
16비트 정수 범위에서 각 비트를 하나씩 확인하며 덧셈을 수행한다.
각 비트의 합은 XOR 연산으로 구하고,
올림수(carry)는 두 비트 이상이 1인 경우를 OR 연산으로 계산한다.
- 전가산기 원리 적용
계산된 각 비트는 ans의 해당 위치에 저장한다.
Python의 int는 고정된 비트 폭을 가지지 않으므로,
최종 결과의 최상위 비트가 1인 경우에는 16비트 음수로 직접 변환한다.
MASK와 XOR하여 16비트 범위 안에서 비트를 반전한 뒤,
~ 연산을 적용하여 Python의 음수 정수 형태로 변환한다.
"""
class Solution:
def getSum(self, a: int, b: int) -> int:
MASK = 0xFFFF
CHECK = 0x8000
ans = carry = 0
for i in range(16):
a_bit = a & 1
b_bit = b & 1
left = a_bit ^ b_bit ^ carry
carry = (a_bit & b_bit) | (b_bit & carry) | (a_bit & carry)
ans |= left * 2**i
a >>= 1
b >>= 1
if ans & CHECK == 0:
return ans
else:
return ~(MASK ^ ans)- 패턴: Bit Manipulation, Greedy
- 설명: 두 정수의 각 비트를 다루며 덧셈을 비트 연산으로 구현하고, 올림수를 이용해 결과를 구성한다. 비트 단위 연산과 마스크를 활용하는 점에서 비트 조작 패턴에 속하며, 최종 음수 처리 방식도 비트 연산의 활용에 해당한다.
📊 시간/공간 복잡도 분석
| 복잡도 | |
|---|---|
| Time | O(1) |
| Space | O(1) |
피드백: 비트 연산으로 각 비트를 더하고 캐리를 업데이트하는 전가산기 스타일의 구현이다.
개선 제안: 현재 구현이 적절해 보입니다.
💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!
|
@DaleSeo 의논해주신 부분 보고 수정했습니다! 바쁘신데 댓글 감사합니다!!! |
답안 제출 문제
작성자 체크 리스트
In Review로 설정해주세요.검토자 체크 리스트
Important
본인 답안 제출 뿐만 아니라 다른 분 PR 하나 이상을 반드시 검토를 해주셔야 합니다!