-
-
Notifications
You must be signed in to change notification settings - Fork 361
[alphaorderly] WEEK 09 Solutions #2822
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
base: main
Are you sure you want to change the base?
Changes from all commits
64bd1c3
c112ef3
34105e6
375d1fe
0099888
cdb9b50
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,35 @@ | ||
|
|
||
| """ | ||
| 시간복잡도: 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 |
|
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. 🏷️ 알고리즘 패턴 분석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
📊 시간/공간 복잡도 분석
피드백: pos와 neg를 각각 현재까지의 양수/음수 구간의 최대/최소 곱으로 관리한다. 음수의 등장 시 교환이 핵심이다. 개선 제안: 특정 입력에 대해 nums가 비어있을 수 있는 경우에 대비한 입력 검증을 추가하면 안정성이 올라갑니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| """ | ||
| 시간복잡도: 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 |
|
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. 🏷️ 알고리즘 패턴 분석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]
📊 시간/공간 복잡도 분석
피드백: Window 클래스를 통해 필요한 문자 수를 추적하고 키 카운트로 윈도우를 축소한다. 추가 정보 관리로 공간을 상수로 유지한다. 개선 제안: Window 구현에서 Counter/ defaultdict 초기화 비용 고려 시, 정규 딕셔너리 기본값 사용 여부를 재점검해 성능 여지를 확인해볼 수 있습니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| """ | ||
|
|
||
| 시간복잡도: 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] |
|
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. 🏷️ 알고리즘 패턴 분석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
📊 시간/공간 복잡도 분석
피드백: 각 칸에 두 바다 도달 여부를 비트로 표시하고, 양 방향으로 BFS를 확장한다. 중복 방문 최소화가 핵심이다. 개선 제안: 메모리 사용을 줄이고자 비트마스킹 대신 두 개의 독립 체크 배열을 사용하는 방식도 고려 가능하다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| """ | ||
| 시간복잡도: 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 |
|
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. 🏷️ 알고리즘 패턴 분석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)
📊 시간/공간 복잡도 분석
피드백: 비트 연산으로 덧셈의 각 비트를 차례대로 계산하고 최종 음수 표시를 처리한다. 파이썬의 가변 정수 특성을 활용해 경계 처리를 한다. 개선 제안: 입력 수의 범위를 명시적으로 확인하고, 더 큰 비트 수에서도 동작하도록 일반화하는 전략도 고려해볼 만하다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| """ | ||
| 시간복잡도: 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) |
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.
🏷️ 알고리즘 패턴 분석
linked-list-cycle/alphaorderly.py
📊 시간/공간 복잡도 분석
피드백: 토끼와 거북이 알고리즘으로 순차 탐색 중 사이클 여부를 빠르게 판단한다. 추가 공간은 상수로 유지된다.
개선 제안: 현재 구현은 의도한 대로 동작하지만, 입력 head가 None인 경우를 명시적으로 처리하는 방어 코드가 있으면 더 robust합니다.