From 550782d23bc8d911d76c95af7fb68fe2c97157fe Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 07:26:00 +0900 Subject: [PATCH 1/9] Create taurus09318976.py --- linked-list-cycle/taurus09318976.py | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 linked-list-cycle/taurus09318976.py diff --git a/linked-list-cycle/taurus09318976.py b/linked-list-cycle/taurus09318976.py new file mode 100644 index 000000000..cc572b641 --- /dev/null +++ b/linked-list-cycle/taurus09318976.py @@ -0,0 +1,43 @@ +''' +*문제의 의도 : +이 문제는 연결 리스트에 사이클이 있는지 판단하는 문제임. +사이클이란 어떤 노드에서 시작해서 next 포인터를 계속 이동시키다 다시 원래 노드로 돌아올 수 있는 경우를 말함 + +*해결방법 : 토끼와 거북이 알고리듬을 사용함 +두 개의 포인터를 사용하는데, 느린 포인터(거북이)는 한 번에 한칸씩 이동, +빠른 포인터(토끼)는 한 번에 두 칸씩 이동, 사이클이 존재한다면 빠른 포인터가 느린 포인터를 따라잡게 됨. + +*시간 복잡도와 공간 복잡도 +시간 복잡도: O(n) +사이클이 없는 경우: 빠른 포인터가 n/2번 이동하여 끝에 도달하므로 O(n) +사이클이 있는 경우: 최악의 경우 사이클 내에서 느린 포인터와 빠른 포인터가 만날 때까지 사이클 길이만큼 추가로 이동하지만, 전체적으로는 여전히 O(n) + +공간 복잡도: O(1) +두 개의 포인터 변수만 사용하므로 상수 공간만 필요 +입력 크기에 관계없이 일정한 메모리 사용 + +''' + +class Solution: + def hasCycle(self, head: Optional[ListNode]) -> bool: + # 연결 리스트가 비어 있거나 노드가 하나뿐이면 사이클이 불가능하므로 False 반환 + if not head or not head.next: + return False + + # 두 포인터를 모두 head에서 시작하도록 초기화 + slow = head # 느린 포인터 (거북이) + fast = head # 빠른 포인터 (토끼) + + # 빠른 포인터가 두 칸씩 이동하므로, fast와 fast.next 모두 존재하는지 확인 + while fast and fast.next: + slow = slow.next # 느린 포인터는 한 칸씩 이동 + fast = fast.next.next # 빠른 포인터는 두 칸씩 이동 + + # 두 포인터가 같은 노드를 가리키면 사이클이 존재함을 의미 + if slow == fast: + return True + + # 빠른 포인터가 끝에 도달하면 사이클이 없다는 의미 + return False + + From ef02ce2147aa22f0b2c899311a14cdf527eeb77d Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 08:06:58 +0900 Subject: [PATCH 2/9] Create taurus09318976.py --- pacific-atlantic-water-flow/taurus09318976.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 pacific-atlantic-water-flow/taurus09318976.py diff --git a/pacific-atlantic-water-flow/taurus09318976.py b/pacific-atlantic-water-flow/taurus09318976.py new file mode 100644 index 000000000..a3119513f --- /dev/null +++ b/pacific-atlantic-water-flow/taurus09318976.py @@ -0,0 +1,71 @@ +''' +문제의 핵심 포인트: +물은 현재 높이보다 낮거나 같은 인접 셀로만 흐를 수 있음 +즉, heights[현재] ≥ heights[인접]일 때 물이 흐름 +태평양이 왼쪽, 위쪽 경계 +대서양이 오른쪽, 아래쪽 경계 + +해결방법 : +역방향 접근법을 사용함. 즉, 바다에서 거꾸로 추적해서 어떤 셀들이 그 바다에 물을 보낼 수 있는지 찾는 방식임. +이 방식이 각 셀을 개별적으로 확인할 필요가 없고, 시간 복잡도가 O(mxn)으로 최적화 되어 더 효율적임. +1. 태평양에서 시작해서 역류할 수 있는 모든 셀 찾기 +2. 대서양에서 시작해서 역류할 수 있는 모든 셀 찾기 +3. 두 바다 모두에서 도달 가능한 셀들의 교집합 구하기 + + +시간 복잡도: O(m × n) +각 셀을 최대 한 번씩만 방문하므로 O(m × n) +DFS는 각 셀에서 최대 한 번만 실행됨 (집합을 통한 중복 방문 방지) + +공간 복잡도: O(m × n) +pacific_reachable과 atlantic_reachable 집합이 각각 최대 m × n개의 좌표 저장 +DFS 재귀 호출 스택의 최대 깊이도 O(m × n) + +''' + +class Solution: + def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: + # 격자의 크기를 구함 + m, n = len(heights), len(heights[0]) + + # 각 바다에서 도달 가능한 셀들을 저장할 집합 생성 + pacific_reachable = set() + atlantic_reachable = set() + + # DFS 함수 정의 : 현재 위치(r,c),도달 가능한 집합, 이전 셀의 높이를 매개변수로 받음 + def dfs(r, c, reachable, prev_height): + # 경계를 벗어나거나 이미 방문했거나, 현재 높이가 이전 높이보다 낮으면 탐색 종료 + if (r < 0 or r >= m or c < 0 or c >= n or + (r, c) in reachable or heights[r][c] < prev_height): + return + + # 현재 셀을 도달 가능한 집합에 추가 + reachable.add((r, c)) + + # 4방향으로 재귀적으로 DFS 탐색 + dfs(r + 1, c, reachable, heights[r][c]) # 아래 + dfs(r - 1, c, reachable, heights[r][c]) # 위 + dfs(r, c + 1, reachable, heights[r][c]) # 오른쪽 + dfs(r, c - 1, reachable, heights[r][c]) # 왼쪽 + + # 태평양 경계(위쪽, 왼쪽 경계)에서 시작하여 역류 가능한 모든 셀 탐색 + for i in range(m): + dfs(i, 0, pacific_reachable, 0) # 왼쪽 경계 + for j in range(n): + dfs(0, j, pacific_reachable, 0) # 위쪽 경계 + + # 대서양 경계에서 DFS 시작 (아래쪽, 오른쪽 경계) + for i in range(m): + dfs(i, n - 1, atlantic_reachable, 0) # 오른쪽 경계 + for j in range(n): + dfs(m - 1, j, atlantic_reachable, 0) # 아래쪽 경계 + + # 두 바다 모두에서 도달 가능한 셀들의 교집합 구해서 결과 리스트에 추가 + result = [] + for r, c in pacific_reachable & atlantic_reachable: + result.append([r, c]) + + return result + + + \ No newline at end of file From 026942ed5bd0cdf4db1e624078d047bb7b9b40d1 Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 08:07:59 +0900 Subject: [PATCH 3/9] Update taurus09318976.py --- pacific-atlantic-water-flow/taurus09318976.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pacific-atlantic-water-flow/taurus09318976.py b/pacific-atlantic-water-flow/taurus09318976.py index a3119513f..0c913146b 100644 --- a/pacific-atlantic-water-flow/taurus09318976.py +++ b/pacific-atlantic-water-flow/taurus09318976.py @@ -68,4 +68,4 @@ def dfs(r, c, reachable, prev_height): return result - \ No newline at end of file + From df20a1b0b17d9d9fffc56ab44920a6797c623eed Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 08:33:07 +0900 Subject: [PATCH 4/9] Create taurus09318976.py --- maximum-product-subarray/taurus09318976.py | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 maximum-product-subarray/taurus09318976.py diff --git a/maximum-product-subarray/taurus09318976.py b/maximum-product-subarray/taurus09318976.py new file mode 100644 index 000000000..9bd3b6196 --- /dev/null +++ b/maximum-product-subarray/taurus09318976.py @@ -0,0 +1,59 @@ +''' +문제의 의도 +이 문제는 배열에서 연속된 부분 배열의 곱이 최대가 되는 값을 찾는 문제임. +음수가 있기 때문에 단순히 최대값만 추적하면 안 되고, 동시에 최소값도 추적해야 함. + +핵심 포인트 +음수 × 음수 = 양수 (큰 양수가 될 수 있음) +음수 × 양수 = 음수 (작아짐) +0이 있으면 곱이 0이 됨 + +해결 방법 +동적 프로그래밍을 사용하되, 최대값과 최소값을 동시에 추적: + +최대값: 양수 결과를 위해 +최소값: 음수가 나중에 양수로 바뀔 가능성을 위해 + +시간 복잡도: O(n) +배열을 한 번만 순회하므로 O(n) +각 원소에서 상수 시간의 연산만 수행 + +공간 복잡도: O(1) +추가 배열을 사용하지 않음 +몇 개의 변수만 사용하므로 상수 공간 +''' + +class Solution: + def maxProduct(self, nums: List[int]) -> int: + # 빈 배열 처리 + if not nums: + return 0 + + # 첫 번째 원소로 모든 값들을 초기화 + max_product = nums[0] # 전체 최대값 + current_max = nums[0] # 현재 위치까지의 최대 곱 + current_min = nums[0] # 현재 위치까지의 최소 곱 + + # 두 번째 원소부터 배열을 순회하며 현재 숫자를 저장 + for i in range(1, len(nums)): + num = nums[i] + + # 현재 숫자가 음수일 경우를 대비해 max와 min을 임시 저장 + temp_max = current_max + + # 새로운 최대값 계산: 최대값과 최소값 모두 아래의 3개 중에서 나올 수 있음 + # num : 현재 숫자부터 새로 시작 + # temp_max * num : 현재숫자 × 이전최대값 + # current_min * num : 현재숫자 × 이전최소값(음수*음수=양수 가능성 떄문에) + current_max = max(num, temp_max * num, current_min * num) + + # 새로운 최소값 계산: 나중에 음수와 곱해져서 큰 양수가 될 가능성을 위해 + # 현재 숫자, 현재숫자×이전최대, 현재숫자×이전최소 중 최소 + current_min = min(num, temp_max * num, current_min * num) + + # 지금까지의 전체 최대값과 현재 최대값을 비교하여 업데이트 + max_product = max(max_product, current_max) + + return max_product + + From f9bd36c99ed2e8bd0613f4c27cceea78299d4322 Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 09:52:01 +0900 Subject: [PATCH 5/9] Create taurus09318976.py --- sum-of-two-integers/taurus09318976.py | 91 +++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 sum-of-two-integers/taurus09318976.py diff --git a/sum-of-two-integers/taurus09318976.py b/sum-of-two-integers/taurus09318976.py new file mode 100644 index 000000000..6756a9693 --- /dev/null +++ b/sum-of-two-integers/taurus09318976.py @@ -0,0 +1,91 @@ +''' +문제의 의도 : +이 문제는 + 와 - 연산자를 사용하지 않고 두 정수의 합을 구하는 문제임. +컴퓨터가 내부적으로 덧셈을 어떻게 처리하는지 이해하는 것이 핵심임. + +해결 방법 : +비트 연산을 사용함 +XOR (^): 자리수별 덧셈 (올림 없이) +AND (&): + 왼쪽 시프트 (<<): 올림(carry) 계산 +올림이 0이 될 때까지 반복 + +**비트 연산 덧셈 원리 +1단계: XOR (^) - "올림 없는 덧셈" +예시: 5 + 3을 이진수로 + + 101 (5) + + 011 (3) + ------ + +2^0의 자리 1 + 1 = 0 (올림 무시) +2^1의 자리 0 + 1 = 1 +2^1의 자리 1 + 1 = 0 (올림 무시) + + 101 +^ 011 +----- + 110 (6) ← 이것이 XOR 결과 + +2단계: AND (&) + 시프트 (<<) - "올림 계산" +어디서 올림이 발생하는가? +→ 둘 다 1인 자리에서만 올림 발생 + + 101 +& 011 +----- + 001 ← 올림이 발생하는 자리 + +올림을 다음 자리로 이동: << 1 (왼쪽으로 1칸 시프트) + +3단계: 다시 1단계와 2단계 반복 +이제 6 + 2를 계산: + +1단계: 110 ^ 010 = 100 (4) +2단계: (110 & 010) << 1 = 010 << 1 = 100 (4) + +다시 4 + 4를 계산: +1단계: 100 ^ 100 = 000 (0) +2단계: (100 & 100) << 1 = 100 << 1 = 1000 (8) + +다시 0 + 8을 계산: +1단계: 000 ^ 1000 = 1000 (8) +2단계: (000 & 1000) << 1 = 000 << 1 = 0 + +-> 올림이 0이 되었으므로 종료. 결과는 8 (1000) + +시간 복잡도: O(1) +32비트 정수에서 최대 32번의 반복만 필요 +올림이 전파되는 최대 거리가 32비트로 제한됨 + +공간 복잡도: O(1) +몇 개의 변수만 사용하므로 상수 공간 +추가 배열이나 자료구조 사용하지 않음 +''' +class Solution: + def getSum(self, a: int, b: int) -> int: + # 32비트 정수 범위를 위한 마스크 생성 + # why? Python의 무한 정밀도 정수를 32비트로 제한하기 위해 사용 + mask = 0xFFFFFFFF + + # 올림이 0이 될 때까지 반복 + # b가 올림을 나타내므로, 올림이 없으면 덧셈 완료 + while b != 0: + # XOR 연산으로 올림 없는 덧셈 수행 + sum_without_carry = a ^ b + + # AND 연산으로 올림이 발생하는 자리 찾기 + # 왼쪽으로 1비트 시프트하여 올림을 다음 자리로 이동 + carry = (a & b) << 1 + + # 32비트 범위로 제한하면서 다음 반복을 위해 값 업데이트 + a = sum_without_carry & mask + b = carry & mask + + # 음수 처리: 32비트에서 최대 양수(0x7FFFFFFF)보다 크면 음수로 변환 + if a > 0x7FFFFFFF: + return ~(a ^ mask) + else: + return a + + + From 8703d5a7108b7202a63f588a6fef04d602f9eb5a Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 10:05:46 +0900 Subject: [PATCH 6/9] Create taurus09318976.py --- minimum-window-substring/taurus09318976.py | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 minimum-window-substring/taurus09318976.py diff --git a/minimum-window-substring/taurus09318976.py b/minimum-window-substring/taurus09318976.py new file mode 100644 index 000000000..b0681380c --- /dev/null +++ b/minimum-window-substring/taurus09318976.py @@ -0,0 +1,103 @@ +''' +문제의 의도 +이 문제는 문자열 s에서 문자열 t의 모든 문자(중복 포함)를 포함하는 가장 짧은 부분 문자열을 찾는 문제임. + +핵심 포인트: +1) t의 모든 문자가 포함되어야 함 (중복 개수도 맞아야 함) +2) 가장 짧은 길이의 부분 문자열을 찾아야 함 +3) 연속된 부분 문자열이어야 함 + +해결 방법 +슬라이딩 윈도우 + 투 포인터 기법을 사용함: +오른쪽 포인터로 윈도우를 확장하여 조건을 만족시킴 +조건을 만족하면 왼쪽 포인터로 윈도우를 축소하여 최소 길이 찾기 +해시맵으로 문자 개수를 효율적으로 관리 + +Example 1의 경우를 보면, + +Input: s = "ADOBECODEBANC", t = "ABC" + +실행 과정: +t_count: {'A':1, 'B':1, 'C':1}, required = 3 +윈도우 확장: right 포인터로 "ADOBEC"까지 확장 → 조건 만족 +윈도우 축소: left 포인터로 "ODEBANC" → "BANC"까지 축소 +최종 결과: "BANC" (길이 4) + +Output: "BANC" + +시간 복잡도: O(|s| + |t|) +각 문자가 최대 2번 방문됨 (right 포인터로 1번, left 포인터로 1번) +t를 한 번 순회하여 해시맵 생성: O(|t|) +전체적으로 선형 시간 복잡도 + +공간 복잡도: O(|s| + |t|) +t_count 해시맵: O(|t|) +window_counts 해시맵: 최대 O(|s|) +기타 변수들: O(1) +''' + +class Solution: + def minWindow(self, s: str, t: str) -> str: + # t가 빈 문자열이면 빈 문자열 반환 + if not t: + return "" + + # t의 각 문자 개수를 저장하는 해시맵 + t_count = {} + for char in t: + t_count[char] = t_count.get(char, 0) + 1 + + # 조건을 만족하기 위해 필요한 고유 문자의 개수 + required = len(t_count) + + # 슬라이딩 윈도우의 왼쪽, 오른쪽 포인터와 조건을 만족하는 문자 개수 초기화 + left = 0 + right = 0 + + # 현재 윈도우에서 조건을 만족하는 문자의 개수 + formed = 0 + + # 현재 윈도우의 문자 개수를 저장하는 해시맵 + window_counts = {} + + # 결과를 저장할 변수들 + min_len = float('inf') + min_left = 0 + min_right = 0 + + # 오른쪽 포인터로 윈도우 확장 + while right < len(s): + # 오른쪽 문자를 윈도우에 추가 + char = s[right] + window_counts[char] = window_counts.get(char, 0) + 1 + + # 현재 문자의 개수가 t에서 요구하는 개수와 같아지면 formed에 1을 더함 + if char in t_count and window_counts[char] == t_count[char]: + formed += 1 + + # 모든 문자의 조건이 만족되면 왼쪽 포인터로 윈도우를 최소화 시도 + while left <= right and formed == required: + char = s[left] + + # 현재 윈도우가 지금까지의 최소 길이보다 작으면 결과 업데이트 + if right - left + 1 < min_len: + min_len = right - left + 1 + min_left = left + min_right = right + + # 왼쪽 문자를 제거하고, 조건을 만족하지 않게 되면 formed에서 1을 뺌 + window_counts[char] -= 1 + if char in t_count and window_counts[char] < t_count[char]: + formed -= 1 + + # 왼쪽 포인터 이동 + left += 1 + + # 오른쪽 포인터 이동 + right += 1 + + # 결과 반환 + return "" if min_len == float('inf') else s[min_left:min_right + 1] + + + From 79e583b636c358ea7b61506282b503e7865b326b Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 10:52:20 +0900 Subject: [PATCH 7/9] Create taurus09318976.py --- reverse-bits/taurus09318976.py | 80 ++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 reverse-bits/taurus09318976.py diff --git a/reverse-bits/taurus09318976.py b/reverse-bits/taurus09318976.py new file mode 100644 index 000000000..c9e959307 --- /dev/null +++ b/reverse-bits/taurus09318976.py @@ -0,0 +1,80 @@ +''' +문제의 의도: +이 문제는 32비트 부호 없는 정수의 비트를 뒤집는 문제임. +즉, 이진수 표현에서 첫 번째 비트와 마지막 비트를 바꾸고, +두 번째 비트와 31번째 비트를 바꾸는 식으로 모든 비트의 순서를 뒤집어야 함. + +해결 방법: +if문을 사용해서 마지막 비트가 1인지 확인하고, 1이면 결과에 추가하는 방식. +이 방법은 "동전 줍기"와 같음 +원본에서 동전(비트) 하나씩 확인 +동전이 있으면(비트가 1이면) 주머니(result)에 넣기 +동전이 없으면(비트가 0이면) 그냥 넘어가기 +이렇게 하면 자연스럽게 순서가 뒤바뀜 + +Example 1.의 경우 +Input: n = 43261596 (이진수: 00000010100101000001111010011100) + +1번째 반복: +- result = 0 << 1 = 0 +- n & 1 = 0 (마지막 비트가 0) +- if문 실행 안 됨 (result는 그대로 0) +- n = n >> 1 = 0000001010010100000111101001110 + +2번째 반복: +- result = 0 << 1 = 0 +- n & 1 = 0 (마지막 비트가 0) +- if문 실행 안 됨 (result는 그대로 0) +- n = n >> 1 = 000000101001010000011110100111 + +3번째 반복: +- result = 0 << 1 = 0 +- n & 1 = 1 (마지막 비트가 1) +- if문 실행: result = 0 + 1 = 1 +- n = n >> 1 = 00000010100101000001111010011 + +4번째 반복: +- result = 1 << 1 = 10 (이진수) +- n & 1 = 1 (마지막 비트가 1) +- if문 실행: result = 10 + 1 = 11 (이진수) +- n = n >> 1 = 0000001010010100000111101001 + +... (32번 반복) + +최종: result = 00111001011110000010100101000000 +Output: 964176192 + +시간 복잡도: O(1) +항상 정확히 32번의 반복을 수행 +입력 크기에 관계없이 상수 시간 + +공간 복잡도: O(1) +몇 개의 변수만 사용하므로 상수 공간 +추가 배열이나 자료구조 사용하지 않음 + +''' +class Solution: + def reverseBits(self, n: int) -> int: + # 뒤집힌 비트들을 저장할 결과 변수를 0으로 초기화 + result = 0 + + # 32비트를 모두 처리하기 위해 32번 반복 + for i in range(32): + # 결과를 왼쪽으로 1비트 시프트하여 새로운 비트를 넣을 자리 만들기 + # 예: 101 → 1010 (오른쪽에 0이 하나 추가됨) + result = result << 1 + + # n & 1로 n의 가장 오른쪽 비트를 추출 + # 그 비트가 1인지 확인 + if n & 1 == 1: + # 마지막 비트가 1이었다면 result의 가장 오른쪽에 1을 추가 + # 0이었다면 아무것도 안 함 (자동으로 0이 추가된 상태) + result = result + 1 + + # n을 오른쪽으로 1비트 시프트하여 다음 비트를 처리할 준비 + n = n >> 1 + + return result + + + From 94dfa2e8b6e34912cc4600439b1061383d86f34f Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 12:55:35 +0900 Subject: [PATCH 8/9] Create taurus09318976.py --- .../taurus09318976.py | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 longest-repeating-character-replacement/taurus09318976.py diff --git a/longest-repeating-character-replacement/taurus09318976.py b/longest-repeating-character-replacement/taurus09318976.py new file mode 100644 index 000000000..7cf80427c --- /dev/null +++ b/longest-repeating-character-replacement/taurus09318976.py @@ -0,0 +1,80 @@ +''' +문제의 의도 +이 문제는 문자열에서 최대 k번의 문자 변경을 통해 같은 문자로만 이루어진 가장 긴 부분 문자열을 찾는 문제임. + +핵심 포인트: +최대 k번까지 문자를 다른 문자로 바꿀 수 있음 +바꾼 후 같은 문자로만 이루어진 부분 문자열의 최대 길이를 구해야 함 +연속된 부분 문자열이어야 함 + +해결 방법 +슬라이딩 윈도우(Sliding Window) + 해시맵 기법을 사용함: +윈도우 내에서 가장 빈도가 높은 문자를 찾기 +나머지 문자들을 가장 빈도가 높은 문자로 바꾸는 데 필요한 변경 횟수 계산 +변경 횟수가 k를 초과하면 윈도우 축소 + +실행 과정: +윈도우 "A": max_freq=1, 변경 필요=0 ≤ k=1 +윈도우 "AA": max_freq=2, 변경 필요=0 ≤ k=1 +윈도우 "AAB": max_freq=2, 변경 필요=1 ≤ k=1 +윈도우 "AABA": max_freq=3, 변경 필요=1 ≤ k=1 +윈도우 "AABAB": max_freq=3, 변경 필요=2 > k=1 +윈도우 축소: "ABAB" → max_freq=2, 변경 필요=2 > k=1 +계속 축소: "BAB" → max_freq=2, 변경 필요=1 ≤ k=1 +Output: 4 (윈도우 "AABA"에서 B 하나를 A로 바꾸면 "AAAA") + +핵심 아이디어: +윈도우 내에서 가장 많은 문자를 기준으로 하고, +나머지 문자들을 그 문자로 바꾸는 데 필요한 변경 횟수가 k 이하인지 확인. + +시간 복잡도: O(n) +각 문자가 최대 2번 방문됨 (right 포인터로 1번, left 포인터로 1번) +해시맵 연산은 O(1)이므로 전체적으로 선형 시간 + +공간 복잡도: O(1) +영어 대문자만 사용하므로 해시맵 크기는 최대 26개로 상수 +기타 변수들도 상수 공간 +''' + +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + # 현재 윈도우 내 각 문자의 개수를 저장하는 해시맵 + char_count = {} + + # 슬라이딩 윈도우의 시작점 + left = 0 + + # 윈도우 내에서 가장 빈도가 높은 문자의 개수 + max_freq = 0 + + # 결과로 반환할 최대 길이 + max_length = 0 + + # 오른쪽 포인터로 문자열을 순회하며 윈도우 확장 + for right in range(len(s)): + # 현재 문자를 윈도우에 추가하고 빈도 증가 + char = s[right] + char_count[char] = char_count.get(char, 0) + 1 + + # 현재 윈도우에서 가장 빈도가 높은 문자의 개수 업데이트 + max_freq = max(max_freq, char_count[char]) + + # 현재 윈도우 크기 + window_size = right - left + 1 + + # 변경해야 할 문자 개수 = 전체 윈도우 크기 - 가장 많은 문자 개수 + # 이 값이 k보다 크면 윈도우가 유효하지 않음 + if window_size - max_freq > k: + # 왼쪽 문자를 윈도우에서 제거하고 왼쪽 포인터 이동 + left_char = s[left] + char_count[left_char] -= 1 + left += 1 + + # 현재 윈도우 크기로 최대 길이 업데이트 + max_length = max(max_length, right - left + 1) + + return max_length + + + + From e1b17a4141eb3fe4bf558b7c0ddaa5f10ad75f54 Mon Sep 17 00:00:00 2001 From: "Dahm-won (Abby) Heo" Date: Sun, 1 Jun 2025 13:08:12 +0900 Subject: [PATCH 9/9] Create taurus09318976.py --- clone-graph/taurus09318976.py | 79 +++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 clone-graph/taurus09318976.py diff --git a/clone-graph/taurus09318976.py b/clone-graph/taurus09318976.py new file mode 100644 index 000000000..34e02677e --- /dev/null +++ b/clone-graph/taurus09318976.py @@ -0,0 +1,79 @@ +""" +문제의 핵심 포인트: +각 노드는 값과 이웃 노드들의 리스트를 가짐 +원본 그래프와 완전히 독립적인 새로운 그래프 생성 +모든 연결 관계를 그대로 복사 + +해결 방법: +DFS(깊이 우선 탐색) + 해시맵을 사용함: +해시맵으로 원본 노드와 복사본 노드의 매핑 관계 저장 +DFS로 그래프를 순회하면서 각 노드를 복사 +이미 복사된 노드는 해시맵에서 찾아서 재사용 + +Example 1의 경우 +1 --- 2 +| | +| | +4 --- 3 + +실행 과정: +노드 1 복사: clone_1 생성, visited = clone_1 +노드 1의 이웃 2 복사: clone_2 생성, visited = clone_2 +노드 2의 이웃 1 처리: 이미 visited에 있으므로 clone_1 반환 +노드 2의 이웃 3 복사: clone_3 생성, visited = clone_3 +노드 3의 이웃들 처리: clone_2, clone_4 연결 +노드 1의 이웃 4 복사: clone_4 생성, visited = clone_4 +모든 연결 관계 완성 +Output: 원본과 동일한 구조의 완전히 새로운 그래프 + +시간 복잡도: O(V + E) +V: 노드(정점)의 개수, E: 간선의 개수 +각 노드를 한 번씩 방문하고, 각 간선을 한 번씩 처리 +DFS의 표준 시간 복잡도 + +공간 복잡도: O(V) +visited 해시맵이 모든 노드를 저장: O(V) +DFS 재귀 호출 스택의 최대 깊이: O(V) +복사된 그래프 자체도 O(V + E)의 공간 필요 +""" + +from typing import Optional +class Solution: + def cloneGraph(self, node: Optional['Node']) -> Optional['Node']: + # 빈 그래프인 경우 None 반환 + if not node: + return None + + # 원본 노드를 키로, 복사본 노드를 값으로 하는 해시맵 생성 + # 중복 복사를 방지하고 순환 참조 문제 해결 + visited = {} + + # DFS 함수 정의 + def dfs(original_node): + # 이미 복사된 노드라면 기존 복사본을 반환 (중복 복사 방지) + if original_node in visited: + return visited[original_node] + + # 원본 노드의 값으로 새로운 노드 생성 + # 이웃 리스트는 빈 리스트로 초기화 + clone_node = Node(original_node.val, []) + + # 해시맵에 원본-복사본 매핑 관계 저장 + # 순환 참조 방지를 위해 이웃을 처리하기 전에 먼저 저장 + visited[original_node] = clone_node + + # 원본 노드의 모든 이웃들을 재귀적으로 복사 + # 복사된 이웃 노드들을 현재 복사본의 이웃 리스트에 추가 + for neighbor in original_node.neighbors: + # 이웃 노드를 복사하고 현재 노드의 이웃 리스트에 추가 + clone_node.neighbors.append(dfs(neighbor)) + + # 완성된 복사본 노드 반환 + return clone_node + + # 주어진 노드부터 DFS 시작 + return dfs(node) + + + +