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
32 changes: 32 additions & 0 deletions clone-graph/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.

🏷️ 알고리즘 패턴 분석

clone-graph/alphaorderly.py
"""
시간복잡도 : O(V + E)
공간복잡도 : O(V)

1. made 딕셔너리를 초기화한다.
2. copy_node 함수를 정의한다.
3. copy_node 함수는 target 노드를 복사한 후 반환한다.
4. 이미 복사한 노드는 made에서 재사용한다.
5. 복사한 노드의 이웃들 또한 copy_node를 이용해 재귀적으로 복사한다.
6. 최종적으로 복제된 그래프의 시작 노드를 반환한다.
"""
class Solution:
    def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:

        made = dict()

        def copy_node(target: Node) -> Node:
            if not target:
                return None

            copied = Node(target.val)
            made[target.val] = copied

            for nei in target.neighbors:
                if nei.val in made:
                    copied.neighbors.append(made[nei.val])
                else:
                    copied.neighbors.append(copy_node(nei))

            return copied

        return copy_node(node)
  • 패턴: Depth-First Search, Hash Map / Hash Set
  • 설명: 그래프의 각 노드를 재귀적으로 방문하며 이웃 노드를 재귀적으로 복제한다. 이미 복제된 노드를 해시 맵으로 재사용하여 순환과 중복 방문을 방지한다.

📊 시간/공간 복잡도 분석

복잡도
Time O(V + E)
Space O(V)

피드백: 각 노드를 한 번씩 방문하고 이웃 노드를 재귀적으로 복제하므로 전체 그래프의 노드 수와 간선 수에 비례하는 시간 복잡도와 노드 수에 비례하는 추가 공간이 필요하다.

개선 제안: 현재 구현이 적절해 보입니다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
시간복잡도 : O(V + E)
공간복잡도 : O(V)

1. made 딕셔너리를 초기화한다.
2. copy_node 함수를 정의한다.
3. copy_node 함수는 target 노드를 복사한 후 반환한다.
4. 이미 복사한 노드는 made에서 재사용한다.
5. 복사한 노드의 이웃들 또한 copy_node를 이용해 재귀적으로 복사한다.
6. 최종적으로 복제된 그래프의 시작 노드를 반환한다.
"""
class Solution:
def cloneGraph(self, node: Optional["Node"]) -> Optional["Node"]:

made = dict()

def copy_node(target: Node) -> Node:
if not target:
return None

copied = Node(target.val)
made[target.val] = copied

for nei in target.neighbors:
if nei.val in made:
copied.neighbors.append(made[nei.val])
else:
copied.neighbors.append(copy_node(nei))

return copied

return copy_node(node)
34 changes: 34 additions & 0 deletions longest-common-subsequence/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.

🏷️ 알고리즘 패턴 분석

longest-common-subsequence/alphaorderly.py
"""
시간복잡도 : O(T1 * T2)
공간복잡도 : O(T1) # T1에 짧은 문자열을 설정했기에 공간복잡도는 T1이 된다.

1. text1과 text2 중 더 짧은 쪽을 text1으로 설정한다.
2. T1과 T2를 각각 text1과 text2의 길이로 설정한다.
3. dp 배열을 T1 + 1 크기로 0으로 초기화한다.
4. text2의 각 문자(i)를 순회하면서 비교한다.
5. new_dp 배열을 T1 + 1 크기로 0으로 초기화한다.
6. text1의 각 문자(j)에 대해 순회하면서 비교한다.
7. text2[i - 1]과 text1[j - 1]가 같으면, new_dp[j]를 dp[j - 1] + 1로 설정한다.
8. 다르면, new_dp[j]를 max(new_dp[j - 1], dp[j])로 설정한다.
9. dp를 new_dp로 갱신하고, 마지막 원소를 반환한다.
"""
class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        if len(text1) > len(text2):
            text1, text2 = text1, text2

        T1, T2 = len(text1), len(text2)
        dp = [0] * (T1 + 1)

        for i in range(1, T2 + 1):
            new_dp = [0] * (T1 + 1)

            for j in range(1, T1 + 1):
                if text2[i - 1] == text1[j - 1]:
                    new_dp[j] = dp[j - 1] + 1
                else:
                    new_dp[j] = max(new_dp[j - 1], dp[j])

            dp = new_dp

        return dp[-1]
  • 패턴: Dynamic Programming, Two Pointers
  • 설명: 두 문자열의 부분수열 문제를 DP로 해결하는 전형적인 패턴으로, 각 위치의 부분문자열 조합에 대한 최장 공통 부분수열 길이를 점진적으로 계산한다. 또한 길이가 서로 다른 두 문자열에 대해 간접적으로 두 포인터처럼 인덱스 순회와 DP 배열 갱신으로 구성된다.

📊 시간/공간 복잡도 분석

복잡도
Time O(T1 * T2)
Space O(T1)

피드백: 공간을 줄이기 위해 두 열 배열을 교체하며 시간 복잡도는 두 문자열 길이의 곱이다.

개선 제안: 현재 구현이 적절해 보입니다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
시간복잡도 : O(T1 * T2)
공간복잡도 : O(T1) # T1에 짧은 문자열을 설정했기에 공간복잡도는 T1이 된다.

1. text1과 text2 중 더 짧은 쪽을 text1으로 설정한다.
2. T1과 T2를 각각 text1과 text2의 길이로 설정한다.
3. dp 배열을 T1 + 1 크기로 0으로 초기화한다.
4. text2의 각 문자(i)를 순회하면서 비교한다.
5. new_dp 배열을 T1 + 1 크기로 0으로 초기화한다.
6. text1의 각 문자(j)에 대해 순회하면서 비교한다.
7. text2[i - 1]과 text1[j - 1]가 같으면, new_dp[j]를 dp[j - 1] + 1로 설정한다.
8. 다르면, new_dp[j]를 max(new_dp[j - 1], dp[j])로 설정한다.
9. dp를 new_dp로 갱신하고, 마지막 원소를 반환한다.
"""
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
if len(text1) > len(text2):
text1, text2 = text1, text2

T1, T2 = len(text1), len(text2)
dp = [0] * (T1 + 1)

for i in range(1, T2 + 1):
new_dp = [0] * (T1 + 1)

for j in range(1, T1 + 1):
if text2[i - 1] == text1[j - 1]:
new_dp[j] = dp[j - 1] + 1
else:
new_dp[j] = max(new_dp[j - 1], dp[j])

dp = new_dp

return dp[-1]
106 changes: 106 additions & 0 deletions longest-repeating-character-replacement/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.

🏷️ 알고리즘 패턴 분석

longest-repeating-character-replacement/alphaorderly.py
"""
시간복잡도 : O(N)
공간복잡도 : O(1)

1. freq 딕셔너리를 초기화한다.
2. s의 각 문자(value)에 대해 루프를 돈다.
3. freq[value]를 1 증가시킨다.
4. maxima를 현재 윈도우에서 가장 빈도가 높은 문자 빈도로 갱신한다.
5. 윈도우 크기에서 maxima의 값만큼을 뺀 값이 k보다 크면,
6. freq[s[left]]를 1 감소시키고 left를 1 증가시킨다.
7. ans를 윈도우의 최대 길이로 갱신한다.
8. 마지막으로 ans를 반환한다.
"""
class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        freq = defaultdict(int)
        # maxima : 현재 윈도우 내에서 가장 많이 등장한 문자의 빈도수
        maxima = left = ans = 0

        for right, value in enumerate(s):
            freq[value] += 1
            maxima = max(maxima, freq[value])

            while (right - left + 1) - maxima > k:
                freq[s[left]] -= 1
                left += 1

            ans = max(ans, right - left + 1)

        return ans

"""
시간복잡도 : O(log N)
공간복잡도 : O(N)

### 세그먼트 트리 구현 ### (윈도우 내 전체만 사용하므로 query 구현 없음)

#### 세그먼트 트리 원리
- 각 알파벳 빈도 합과 빈도가 가장 높은 문자의 빈도를 저장하는 세그먼트 트리를 사용한다.

#### 코드 설명
- window.update로 알파벳 개수를 갱신하며,
- window.tree[1][0]에서 현재 윈도우 내 전체 문자 수,
- window.tree[1][1]에서 윈도우 내 등장 빈도가 가장 높은 문자의 개수를 구한다.
- 윈도우 크기 - 최대 빈도가 k를 초과하면 left를 옮기며 윈도우를 줄인다.

> 불필요하게 복잡한 구현이지만, 세그먼트 트리를 공부하기엔 좋은 예제가 될 수 있다.
"""
class SegTree:
    def __init__(self):
        # [문자 개수 합, 최대값]
        self.tree = [[0, 0] for _ in range(26 * 4 + 1)]

    def _update(
        self,
        node_index: int,
        target_index: int,
        target_update: int,
        seg_left: int,
        seg_right: int,
    ):
        if seg_left == seg_right:
            self.tree[node_index][0] += target_update
            self.tree[node_index][1] += target_update
            return

        mid = (seg_left + seg_right) // 2

        if target_index <= mid:
            self._update(node_index * 2, target_index, target_update, seg_left, mid)
        else:
            self._update(
                node_index * 2 + 1, target_index, target_update, mid + 1, seg_right
            )

        self.tree[node_index][0] = (
            self.tree[node_index * 2][0] + self.tree[node_index * 2 + 1][0]
        )
        self.tree[node_index][1] = max(
            self.tree[node_index * 2][1], self.tree[node_index * 2 + 1][1]
        )

    def update(self, target: str, update: int):
        self._update(1, ord(target) - ord("A"), update, 0, 25)


class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        left = 0
        window = SegTree()
        ans = 0

        for right, value in enumerate(s):
            window.update(value, 1)

            while window.tree[1][0] > 0 and window.tree[1][0] - window.tree[1][1] > k:
                window.update(s[left], -1)
                left += 1

            ans = max(ans, right - left + 1)

        return ans
  • 패턴: Sliding Window, Greedy
  • 설명: 첫 번째 구현은 슬라이딩 윈도우를 이용해 문자 빈도 차이를 k 이하로 유지하며 윈도우 길이를 확장하는 패턴이고, 두 번째 구현도 윈도우 크기를 조절하며 최대 빈도와의 차이가 k를 넘지 않도록 왼쪽 포인터를 이동하는 그리디 성격의 접근이다. 두 방법 모두 문자열 내 적절한 부분문자열 길이를 최적화한다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 2가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Solution.characterReplacement — Time: O(N) / Space: O(1)
복잡도
Time O(N)
Space O(1)

피드백: 윈도우 안의 문자 빈도수 추적으로 최대 길이를 결정한다. 최대 빈도수를 유지하는 방식이 핵심이다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 2: SegTree — Time: O(log N) / Space: O(N)
복잡도
Time O(log N)
Space O(N)

피드백: 복잡도가 불필요하게 커지는 예제지만 세그먼트 트리 학습에 유용하다.

개선 제안: 현재 구현이 적절해 보입니다.

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

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.

🏷️ 알고리즘 패턴 분석

longest-repeating-character-replacement/alphaorderly.py
"""
시간복잡도 : O(N)
공간복잡도 : O(1)

1. freq 딕셔너리를 초기화한다.
2. s의 각 문자(value)에 대해 루프를 돈다.
3. freq[value]를 1 증가시킨다.
4. maxima를 현재 윈도우에서 가장 빈도가 높은 문자 빈도로 갱신한다.
5. 윈도우 크기에서 maxima의 값만큼을 뺀 값이 k보다 크면,
6. freq[s[left]]를 1 감소시키고 left를 1 증가시킨다.
7. ans를 윈도우의 최대 길이로 갱신한다.
8. 마지막으로 ans를 반환한다.
"""
class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        freq = defaultdict(int)
        # maxima : 현재 윈도우 내에서 가장 많이 등장한 문자의 빈도수
        maxima = left = ans = 0

        for right, value in enumerate(s):
            freq[value] += 1
            maxima = max(maxima, freq[value])

            while (right - left + 1) - maxima > k:
                freq[s[left]] -= 1
                left += 1

            ans = max(ans, right - left + 1)

        return ans

"""
시간복잡도: O(NlogN)
  - 세그먼트 트리 한 번 업데이트: O(logN)
  - 슬라이딩 윈도우 오른쪽 포인터 한 번씩 전진: O(N)
공간복잡도: O(N)
  - 세그먼트 트리 크기: O(N) (알파벳 26개에 대해 4N+1 노드)

[세그먼트 트리 개요]
- 각 알파벳(A~Z)의 빈도수를 표현하는 세그먼트 트리를 구현함.
- 각 트리 노드는 구간 내 현재 알파벳 빈도 총합과 그 구간 내 최빈값(가장 많이 등장한 알파벳의 빈도)을 저장함.
- 이진 트리 구조로 각 알파벳 인덱스를 리프 노드(0~25)에 매핑.

[핵심 동작 설명]
- window.update(문자, ±1): 현재 윈도우에 새 문자를 추가/제거할 때 해당 알파벳의 빈도수를 O(logN)에 갱신.
- window.tree[1][0]: 세그먼트 트리 루트의 첫 번째 값으로, 현재 윈도우 내 전체 문자 개수(윈도우 길이)를 의미.
- window.tree[1][1]: 루트의 두 번째 값으로, 현재 윈도우 내에서 가장 많이 등장한 문자의 등장 횟수를 의미.
- (윈도우 전체길이 - 최빈값) > k 를 만족할 때까지 왼쪽 포인터를 옮기며(=왼쪽 문자 제거), 윈도우가 k개 이하의 문자만 바꾸면 모두 동일하게 만들 수 있는 범위로 축소.
- 매 반복마다 ans를 최대 윈도우 크기로 갱신.

※ 세그먼트 트리 사용은 이 문제에 최적해는 아니나, 자료구조 학습에는 좋은 연습 예제.
"""
class SegTree:
    def __init__(self):
        # [문자 개수 합, 최대값]
        self.tree = [[0, 0] for _ in range(26 * 4 + 1)]

    def _update(
        self,
        node_index: int,
        target_index: int,
        target_update: int,
        seg_left: int,
        seg_right: int,
    ):
        if seg_left == seg_right:
            self.tree[node_index][0] += target_update
            self.tree[node_index][1] += target_update
            return

        mid = (seg_left + seg_right) // 2

        if target_index <= mid:
            self._update(node_index * 2, target_index, target_update, seg_left, mid)
        else:
            self._update(
                node_index * 2 + 1, target_index, target_update, mid + 1, seg_right
            )

        self.tree[node_index][0] = (
            self.tree[node_index * 2][0] + self.tree[node_index * 2 + 1][0]
        )
        self.tree[node_index][1] = max(
            self.tree[node_index * 2][1], self.tree[node_index * 2 + 1][1]
        )

    def update(self, target: str, update: int):
        self._update(1, ord(target) - ord("A"), update, 0, 25)


class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        left = 0
        window = SegTree()
        ans = 0

        for right, value in enumerate(s):
            window.update(value, 1)

            while window.tree[1][0] > 0 and window.tree[1][0] - window.tree[1][1] > k:
                window.update(s[left], -1)
                left += 1

            ans = max(ans, right - left + 1)

        return ans
  • 패턴: Sliding Window, Hash Map / Hash Set, Divide and Conquer
  • 설명: 첫 번째 구현은 슬라이딩 윈도우로 연속 부분 문자열에서 필요 문자 갱신 횟수를 관리한다. freq 해시 맵으로 문자 빈도를 추적하고 윈도우를 좌우 포인터로 조정하는 패턴이 핵심이다. 두 번째 구현은 세그먼트 트리로 윈도우 내부 빈도와 최빈값을 관리하지만 여전히 윈도우 확장/축소의 흐름은 Sliding Window에 가깝다.

📊 시간/공간 복잡도 분석

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

피드백: 해당 풀이에서는 슬라이딩 윈도우를 사용해 각 step에서 필요 최소한의 정보를 유지한다. 다만 주석에 제시된 시간/공간 표기가 실제 구현과 다르므로 구현의 동작에 집중하면 된다.

개선 제안: 현재 구현이 적절해 보입니다.

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

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.

🏷️ 알고리즘 패턴 분석

longest-repeating-character-replacement/alphaorderly.py
"""
시간복잡도 : O(N)
공간복잡도 : O(1)

1. freq 딕셔너리를 초기화한다.
2. s의 각 문자(value)에 대해 루프를 돈다.
3. freq[value]를 1 증가시킨다.
4. maxima를 현재 윈도우에서 가장 빈도가 높은 문자 빈도로 갱신한다.
5. 윈도우 크기에서 maxima의 값만큼을 뺀 값이 k보다 크면,
6. freq[s[left]]를 1 감소시키고 left를 1 증가시킨다.
7. ans를 윈도우의 최대 길이로 갱신한다.
8. 마지막으로 ans를 반환한다.
"""
class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        freq = defaultdict(int)
        # maxima : 현재 윈도우 내에서 가장 많이 등장한 문자의 빈도수
        maxima = left = ans = 0

        for right, value in enumerate(s):
            freq[value] += 1
            maxima = max(maxima, freq[value])

            while (right - left + 1) - maxima > k:
                freq[s[left]] -= 1
                left += 1

            ans = max(ans, right - left + 1)

        return ans

"""
시간복잡도: O(NlogN)
  - 세그먼트 트리 한 번 업데이트: O(logN)
  - 슬라이딩 윈도우 오른쪽 포인터 한 번씩 전진: O(N)
공간복잡도: O(N)
  - 세그먼트 트리 크기: O(N) (알파벳 26개에 대해 4N+1 노드)

[세그먼트 트리 개요]
- 각 알파벳(A~Z)의 빈도수를 표현하는 세그먼트 트리를 구현함.
- 각 트리 노드는 구간 내 현재 알파벳 빈도 총합과 그 구간 내 최빈값(가장 많이 등장한 알파벳의 빈도)을 저장함.
- 이진 트리 구조로 각 알파벳 인덱스를 리프 노드(0~25)에 매핑.

[핵심 동작 설명]
- window.update(문자, ±1): 현재 윈도우에 새 문자를 추가/제거할 때 해당 알파벳의 빈도수를 O(logN)에 갱신.
- window.tree[1][0]: 세그먼트 트리 루트의 첫 번째 값으로, 현재 윈도우 내 전체 문자 개수(윈도우 길이)를 의미.
- window.tree[1][1]: 루트의 두 번째 값으로, 현재 윈도우 내에서 가장 많이 등장한 문자의 등장 횟수를 의미.
- (윈도우 전체길이 - 최빈값) > k 를 만족할 때까지 왼쪽 포인터를 옮기며(=왼쪽 문자 제거), 윈도우가 k개 이하의 문자만 바꾸면 모두 동일하게 만들 수 있는 범위로 축소.
- 매 반복마다 ans를 최대 윈도우 크기로 갱신.

※ 세그먼트 트리 사용은 이 문제에 최적해는 아니나, 자료구조 학습에는 좋은 연습 예제.
"""
class SegTree:
    def __init__(self):
        # summation, largest
        self.tree = [[0, 0] for _ in range(26 * 4 + 1)]

    def _update(
        self,
        node_index: int,
        target_index: int,
        target_update: int,
        seg_left: int,
        seg_right: int,
    ):
        if seg_left == seg_right:
            self.tree[node_index][0] += target_update
            self.tree[node_index][1] += target_update
            return

        mid = (seg_left + seg_right) // 2

        if target_index <= mid:
            self._update(node_index * 2, target_index, target_update, seg_left, mid)
        else:
            self._update(
                node_index * 2 + 1, target_index, target_update, mid + 1, seg_right
            )

        self.tree[node_index][0] = (
            self.tree[node_index * 2][0] + self.tree[node_index * 2 + 1][0]
        )
        self.tree[node_index][1] = max(
            self.tree[node_index * 2][1], self.tree[node_index * 2 + 1][1]
        )

    def update(self, target: str, update: int):
        self._update(1, ord(target) - ord("A"), update, 0, 25)


class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        left = 0
        window = SegTree()
        ans = 0

        for right, value in enumerate(s):
            window.update(value, 1)

            while window.tree[1][0] - window.tree[1][1] > k:
                window.update(s[left], -1)
                left += 1

            ans = max(ans, right - left + 1)

        return ans
  • 패턴: Sliding Window, Hash Map / Hash Set, Dynamic Programming
  • 설명: 첫 번째 코드에서 윈도우 크기를 변동시키며 조건을 만족하는 최대 길이를 찾는 Sliding Window 패턴이다. freq로 문자 빈도를 추적하나 해시 맵/해시셋으로 구현된 핵심 아이디어를 사용한다. 두 번째 코드는 세그먼트 트리로 윈도우 내 총합과 최빈값을 관리하나, 윈도우를 좌우로 확장/축소하는 흐름은 Sliding Window의 확장으로 볼 수 있다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 2가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Solution.characterReplacement — Time: O(n) / Space: O(1)
복잡도
Time O(n)
Space O(1)

피드백: 단일 루프와 상수 개의 추가 변수로 구성되어 시간복잡도는 선형이며, 공간은 고정된 상수 크기의 변수로 구성되어 있다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 2: SegTree.characterReplacement — Time: O(n log N) / Space: O(N)
복잡도
Time O(n log N)
Space O(N)

피드백: 세그먼트 트리로 구현했지만 문제의 최적해는 아닐 수 있으며, 슬라이딩 윈도우의 간단한 구현에 비해 비효율적일 수 있다. 트리 업데이트가 매 문자마다 필요하다.

개선 제안: 고려해볼 만한 대안: 간단한 해시맵+윈도우 방식으로 O(n) 시간과 O(1) 공간에 더 가까운 구현을 우선 시도해볼 것.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""
시간복잡도 : O(N)
공간복잡도 : O(1)

1. freq 딕셔너리를 초기화한다.
2. s의 각 문자(value)에 대해 루프를 돈다.
3. freq[value]를 1 증가시킨다.
4. maxima를 현재 윈도우에서 가장 빈도가 높은 문자 빈도로 갱신한다.
5. 윈도우 크기에서 maxima의 값만큼을 뺀 값이 k보다 크면,
6. freq[s[left]]를 1 감소시키고 left를 1 증가시킨다.
7. ans를 윈도우의 최대 길이로 갱신한다.
8. 마지막으로 ans를 반환한다.
"""
class Solution:
def characterReplacement(self, s: str, k: int) -> int:
freq = defaultdict(int)
# maxima : 현재 윈도우 내에서 가장 많이 등장한 문자의 빈도수
maxima = left = ans = 0

for right, value in enumerate(s):
freq[value] += 1
maxima = max(maxima, freq[value])

while (right - left + 1) - maxima > k:
freq[s[left]] -= 1
left += 1

ans = max(ans, right - left + 1)

return ans

"""
시간복잡도: O(NlogN)
- 세그먼트 트리 한 번 업데이트: O(logN)
- 슬라이딩 윈도우 오른쪽 포인터 한 번씩 전진: O(N)
공간복잡도: O(N)
- 세그먼트 트리 크기: O(N) (알파벳 26개에 대해 4N+1 노드)

[세그먼트 트리 개요]
- 각 알파벳(A~Z)의 빈도수를 표현하는 세그먼트 트리를 구현함.
- 각 트리 노드는 구간 내 현재 알파벳 빈도 총합과 그 구간 내 최빈값(가장 많이 등장한 알파벳의 빈도)을 저장함.
- 이진 트리 구조로 각 알파벳 인덱스를 리프 노드(0~25)에 매핑.

[핵심 동작 설명]
- window.update(문자, ±1): 현재 윈도우에 새 문자를 추가/제거할 때 해당 알파벳의 빈도수를 O(logN)에 갱신.
- window.tree[1][0]: 세그먼트 트리 루트의 첫 번째 값으로, 현재 윈도우 내 전체 문자 개수(윈도우 길이)를 의미.
- window.tree[1][1]: 루트의 두 번째 값으로, 현재 윈도우 내에서 가장 많이 등장한 문자의 등장 횟수를 의미.
- (윈도우 전체길이 - 최빈값) > k 를 만족할 때까지 왼쪽 포인터를 옮기며(=왼쪽 문자 제거), 윈도우가 k개 이하의 문자만 바꾸면 모두 동일하게 만들 수 있는 범위로 축소.
- 매 반복마다 ans를 최대 윈도우 크기로 갱신.

※ 세그먼트 트리 사용은 이 문제에 최적해는 아니나, 자료구조 학습에는 좋은 연습 예제.
"""
class SegTree:
def __init__(self):
# summation, largest
self.tree = [[0, 0] for _ in range(26 * 4 + 1)]

def _update(
self,
node_index: int,
target_index: int,
target_update: int,
seg_left: int,
seg_right: int,
):
if seg_left == seg_right:
self.tree[node_index][0] += target_update
self.tree[node_index][1] += target_update
return

mid = (seg_left + seg_right) // 2

if target_index <= mid:
self._update(node_index * 2, target_index, target_update, seg_left, mid)
else:
self._update(
node_index * 2 + 1, target_index, target_update, mid + 1, seg_right
)

self.tree[node_index][0] = (
self.tree[node_index * 2][0] + self.tree[node_index * 2 + 1][0]
)
self.tree[node_index][1] = max(
self.tree[node_index * 2][1], self.tree[node_index * 2 + 1][1]
)

def update(self, target: str, update: int):
self._update(1, ord(target) - ord("A"), update, 0, 25)


class Solution:
def characterReplacement(self, s: str, k: int) -> int:
left = 0
window = SegTree()
ans = 0

for right, value in enumerate(s):
window.update(value, 1)

while window.tree[1][0] - window.tree[1][1] > k:
window.update(s[left], -1)
left += 1

ans = max(ans, right - left + 1)

return ans
39 changes: 39 additions & 0 deletions palindromic-substrings/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.

🏷️ 알고리즘 패턴 분석

palindromic-substrings/alphaorderly.py
"""
시간복잡도 : O(N^2)
공간복잡도 : O(1)

1. n을 s의 길이로 설정한다.
2. count를 0으로 초기화한다.
3. 각 문자를 중심(center)으로 확장하여 홀수 길이의 팰린드롬을 센다.
   - radius를 0부터 시작해, center - radius >= 0, center + radius < n 이고 s[center - radius] == s[center + radius]인 동안 count를 1 증가, radius += 1 한다.
4. 각 문자 쌍(center, center+1)을 중심으로 확장하여 짝수 길이의 팰린드롬을 센다.
   - radius를 0부터 시작해, center - radius >= 0, center + radius + 1 < n 이고 s[center - radius] == s[center + radius + 1]인 동안 count를 1 증가, radius += 1 한다.
5. 총 팰린드롬 부분 문자열 개수인 count를 반환한다.
"""
class Solution:
    def countSubstrings(self, s: str) -> int:
        n = len(s)
        count = 0

        for center in range(n):
            radius = 0
            # 홀수 길이 팰린드롬 (center를 기준)
            while (
                center - radius >= 0
                and center + radius < n
                and s[center - radius] == s[center + radius]
            ):
                count += 1
                radius += 1

            radius = 0
            # 짝수 길이 팰린드롬 (center, center+1을 기준)
            while (
                center - radius >= 0
                and center + radius + 1 < n
                and s[center - radius] == s[center + radius + 1]
            ):
                count += 1
                radius += 1

        return count
  • 패턴: Two Pointers, Monotonic Stack, Dynamic Programming
  • 설명: 문자열의 중심에서 확장하며 팰린드롬을 탐색하는 방식으로, 홀수/짝수 중심을 각각 확장하는 두 포인터(센터-확장) 기법이다. 각 확장에서 일치 여부를 확인하며 카운트를 증가시키므로 한 위치에서의 비교로 전체를 탐색한다.

📊 시간/공간 복잡도 분석

복잡도
Time O(N^2)
Space O(1)

피드백: 가운데를 확장하는 방식으로 모든 홀/짝수 길이 팰린드롬을 탐지한다.

개선 제안: 현재 구현이 적절해 보입니다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
시간복잡도 : O(N^2)
공간복잡도 : O(1)

1. n을 s의 길이로 설정한다.
2. count를 0으로 초기화한다.
3. 각 문자를 중심(center)으로 확장하여 홀수 길이의 팰린드롬을 센다.
- radius를 0부터 시작해, center - radius >= 0, center + radius < n 이고 s[center - radius] == s[center + radius]인 동안 count를 1 증가, radius += 1 한다.
4. 각 문자 쌍(center, center+1)을 중심으로 확장하여 짝수 길이의 팰린드롬을 센다.
- radius를 0부터 시작해, center - radius >= 0, center + radius + 1 < n 이고 s[center - radius] == s[center + radius + 1]인 동안 count를 1 증가, radius += 1 한다.
5. 총 팰린드롬 부분 문자열 개수인 count를 반환한다.
"""
class Solution:
def countSubstrings(self, s: str) -> int:
n = len(s)
count = 0

for center in range(n):
radius = 0
# 홀수 길이 팰린드롬 (center를 기준)
while (
center - radius >= 0
and center + radius < n
and s[center - radius] == s[center + radius]
):
count += 1
radius += 1

radius = 0
# 짝수 길이 팰린드롬 (center, center+1을 기준)
while (
center - radius >= 0
and center + radius + 1 < n
and s[center - radius] == s[center + radius + 1]
):
count += 1
radius += 1

return count
19 changes: 19 additions & 0 deletions reverse-bits/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.

🏷️ 알고리즘 패턴 분석

reverse-bits/alphaorderly.py
"""
시간복잡도: O(1)  # 32번만 반복하므로 항상 상수 시간 복잡도임
공간복잡도: O(1)

1. ans를 0으로 초기화한다.
3. ans를 왼쪽으로 1비트 시프트한 후, n의 마지막 비트를 OR 연산한다.
4. n을 오른쪽으로 1비트 시프트한다.
- 3, 4 과정을 32번 반복한다.
5. ans를 반환한다.
"""
class Solution:
    def reverseBits(self, n: int) -> int:
        ans = 0

        for _ in range(32):
            ans = (ans << 1) | (n & 1)
            n >>= 1

        return ans
  • 패턴: Bit Manipulation, Divide and Conquer, Greedy
  • 설명: 비트 조작으로 각 비트를 좌우로 이동시키며 결과를 구성하는 패턴으로, 반복문 내에서 비트를 shift/OR로 처리하는 비트 연산 기법이다. 입력 비트를 순차적으로 처리해 최종 값을 얻는 방식은 단순하고 명시적이다.

📊 시간/공간 복잡도 분석

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

피드백: 고정된 비트 수를 반복하며 비트를 뒤집는다.

개선 제안: 현재 구현이 적절해 보입니다.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
시간복잡도: O(1) # 32번만 반복하므로 항상 상수 시간 복잡도임
공간복잡도: O(1)

1. ans를 0으로 초기화한다.
3. ans를 왼쪽으로 1비트 시프트한 후, n의 마지막 비트를 OR 연산한다.
4. n을 오른쪽으로 1비트 시프트한다.
- 3, 4 과정을 32번 반복한다.
5. ans를 반환한다.
"""
class Solution:
def reverseBits(self, n: int) -> int:
ans = 0

for _ in range(32):
ans = (ans << 1) | (n & 1)
n >>= 1

return ans
Loading