Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions linked-list-cycle/alphaorderly.py

Copy link
Copy Markdown
Contributor

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
"""
시간복잡도: 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합니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

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
32 changes: 32 additions & 0 deletions maximum-product-subarray/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
  • 패턴: Dynamic Programming, Two Pointers, Greedy
  • 설명: 연속 부분배열의 최대 곱을 찾기 위해 양수/음수의 곱의 특성을 이용하는 단일 패스 해법으로, 현재 위치의 값과 이전까지의 최댓값/최솟값을 갱신하는 DP 성격의 상태 전개와 이를 이용한 그리디적 선택이 결합되어 있습니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n)
Space O(1)

피드백: 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
64 changes: 64 additions & 0 deletions minimum-window-substring/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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]
  • 패턴: Sliding Window
  • 설명: 주어진 코드는 가변 길이 윈도우를 왼쪽/오른쪽 포인터로 조절하며, 타깃 문자열의 모든 문자를 포함하는 최소 윈도우를 찾는 로직으로 구성되어 있습니다. 윈도우 내부 문자를 관리하고 조건 충족 시 윈도우를 축소하는 흐름이 Sliding Window 패턴에 해당합니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n)
Space O(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]
76 changes: 76 additions & 0 deletions pacific-atlantic-water-flow/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
  • 패턴: Breadth-First Search, Dynamic Programming, Hash Map / Hash Set
  • 설명: 주요 로직이 BFS를 사용해 두 바다로 차례대로 도달 여부를 확장하는 방식이며, 각 칸의 상태를 비트마스킹으로 관리해 두 바다의 도달 여부를 합쳐 결과를 얻는 패턴이 보인다. 또한 상태 갱신과 방문 여부 관리가 DP적 아이디어와 비트마스킹의 활용으로 연결된다.

📊 시간/공간 복잡도 분석

복잡도
Time O(m * n)
Space O(m * n)

피드백: 각 칸에 두 바다 도달 여부를 비트로 표시하고, 양 방향으로 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
39 changes: 39 additions & 0 deletions sum-of-two-integers/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)
  • 패턴: Bit Manipulation
  • 설명: 두 정수의 각 비트를 순회하며, 비트 연산으로 덧셈을 구현하고, 캐리와 합을 비트 단위로 계산하는 방식이다. 16비트 고정 폭 가정과 최종 부호 처리로 비트 조작 패턴이 핵심이다.

📊 시간/공간 복잡도 분석

복잡도
Time O(1)
Space O(1)

피드백: 비트 연산으로 덧셈의 각 비트를 차례대로 계산하고 최종 음수 표시를 처리한다. 파이썬의 가변 정수 특성을 활용해 경계 처리를 한다.

개선 제안: 입력 수의 범위를 명시적으로 확인하고, 더 큰 비트 수에서도 동작하도록 일반화하는 전략도 고려해볼 만하다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

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)
Loading