diff --git a/clone-graph/alphaorderly.py b/clone-graph/alphaorderly.py new file mode 100644 index 0000000000..b46729e32b --- /dev/null +++ b/clone-graph/alphaorderly.py @@ -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) diff --git a/longest-common-subsequence/alphaorderly.py b/longest-common-subsequence/alphaorderly.py new file mode 100644 index 0000000000..3104e1ed80 --- /dev/null +++ b/longest-common-subsequence/alphaorderly.py @@ -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] diff --git a/longest-repeating-character-replacement/alphaorderly.py b/longest-repeating-character-replacement/alphaorderly.py new file mode 100644 index 0000000000..dec52c0c4b --- /dev/null +++ b/longest-repeating-character-replacement/alphaorderly.py @@ -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 diff --git a/palindromic-substrings/alphaorderly.py b/palindromic-substrings/alphaorderly.py new file mode 100644 index 0000000000..1d84b3236b --- /dev/null +++ b/palindromic-substrings/alphaorderly.py @@ -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 diff --git a/reverse-bits/alphaorderly.py b/reverse-bits/alphaorderly.py new file mode 100644 index 0000000000..392b491da7 --- /dev/null +++ b/reverse-bits/alphaorderly.py @@ -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