diff --git a/linked-list-cycle/KwonNayeon.py b/linked-list-cycle/KwonNayeon.py index 13fa2d99d..f180abc7e 100644 --- a/linked-list-cycle/KwonNayeon.py +++ b/linked-list-cycle/KwonNayeon.py @@ -4,27 +4,24 @@ - -10^5 <= Node.val <= 10^5 - pos is -1 or a valid index in the linked-list -Time Complexity: -- Solution 1: O(n) -- Solution 2: O(n) - -Space Complexity: -- Solution 1: O(n) - visited set에 모든 노드를 저장할 수 있음 -- Solution 2: O(1) - 추가 메모리 사용하지 않음 - -풀이방법: -1. 처음엔 직관적인 방법으로 해결, 한 번 마주친 노드를 다시 만나는지를 체크하는 방식 -2. slow, fast 두 개의 노드를 활용, 만약 cycle이 존재하는 경우 fast가 slow와 언젠가 만나게 됨, - 만약 cycle이 없다면 둘은 만나지 않음 -""" + + +Time Complexity: O(n) +- while 루프를 최대 n번 실행 (노드의 개수만큼) + +Space Complexity: O(n) +- visited set에 최대 n개의 노드 저장함 +- set() 조회/삽입에 O(1) + +풀이 방법: +- 한 번 마주친 노드를 다시 만나는지를 체크하는 방식 +""" # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None - -# Solution 1: 한 번 마주친 노드를 다시 만나는지를 체크 class Solution: def hasCycle(self, head: Optional[ListNode]) -> bool: visited = set() @@ -38,14 +35,26 @@ def hasCycle(self, head: Optional[ListNode]) -> bool: return False -# Solution 2: 두 개의 포인터 이용 +""" + + +Time Complexity: O(n) +- while 루프를 최대 n번 실행 (노드의 개수만큼) + +Space Complexity: O(1) +- 투 포인터 이외의 추가 메모리 사용하지 않음 + +풀이 방법: +- 투 포인터 방식 (slow, fast) +- 만약 cycle이 있다면, fast가 slow와 언젠가 만나게 됨 +- cycle이 없다면 둘은 만나지 않음 +""" class Solution: def hasCycle(self, head: Optional[ListNode]) -> bool: if not head: return False - slow = head - fast = head + slow = fast = head while fast and fast.next: slow = slow.next @@ -55,4 +64,3 @@ def hasCycle(self, head: Optional[ListNode]) -> bool: return True return False - diff --git a/maximum-product-subarray/KwonNayeon.py b/maximum-product-subarray/KwonNayeon.py index ddef49b53..d8d1882a6 100644 --- a/maximum-product-subarray/KwonNayeon.py +++ b/maximum-product-subarray/KwonNayeon.py @@ -4,21 +4,21 @@ - -10 <= nums[i] <= 10 - The product of any subarray of nums is guaranteed to fit in a 32-bit integer. -Time Complexity: -- O(n): 배열을 한 번만 순회하면서 각 위치에서 상수 시간 연산만 수행 +Time Complexity: O(n) +- 배열을 한 번만 순회하고, 각 위치에서 상수 시간 연산(max, min, 곱셈)만 수행 -Space Complexity: -- O(1): 고정된 추가 변수만 사용 (curr_max, curr_min, ...) +Space Complexity: O(1) +- 입력 크기와 무관하게 고정된 변수(curr_max, curr_min, result, temp)만 사용 풀이방법: -1. DP로 각 위치에서 가능한 최대곱과 최소곱을 동시에 추적함 -2. 각 위치에서 세 개의 선택지 존재: 새로 시작 vs 이전 최대곱과 곱하기 vs 이전 최소곱과 곱하기 -3. 최소곱이 필요한 이유: 나중에 음수를 만났을 때 최대값이 될 수 있기 때문 +1. DP로 각 위치에서 가능한 최대값과 최소값을 함께 업데이트함 +2. 각 위치에서 세 개의 선택지 존재: 새로 시작 vs 이전 최대값과 곱하기 vs 이전 최소값과 곱하기 +3. 최소값을 계속 업데이트 하는 이유: 음수*음수 = 새로운 최대값, curr_max 업데이트할 때 쓰임 (예시: nums = [2, 3, -2, 4, -1]) 4. 매 단계마다 result 업데이트 고려사항: -1. 값이 0인 경우 → 새로 시작해야 함 -2. temp 변수: curr_max를 업데이트하면 curr_min 계산 시 원래 값이 필요함 +1. 값이 0인 경우 새로 시작해야 함 +2. temp 변수: curr_min 계산 시 curr_max를 업데이트하기 전의 값이 필요함 """ class Solution: def maxProduct(self, nums: List[int]) -> int: @@ -33,5 +33,3 @@ def maxProduct(self, nums: List[int]) -> int: result = max(result, curr_max) return result - - diff --git a/minimum-window-substring/KwonNayeon.py b/minimum-window-substring/KwonNayeon.py index b6730723a..c7f688915 100644 --- a/minimum-window-substring/KwonNayeon.py +++ b/minimum-window-substring/KwonNayeon.py @@ -23,8 +23,13 @@ 3. 최소 길이의 윈도우 반환 메모: -- 풀이 방법을 보고 익힘 -- 해시맵과 슬라이딩 윈도우 관련 다른 문제들을 풀고 이 문제 스스로 풀어보기 +- 관련 문제: + - Two Sum (딕셔너리) + - Ransom Note (Counter) + - Longest Substring Without Repeating (슬라이딩 윈도우) + - Permutation in String (슬라이딩 윈도우 + Counter) + - Find All Anagrams in a String (슬라이딩 윈도우 + Counter) +- 4, 5번 문제 먼저 풀어보기 """ class Solution: def minWindow(self, s: str, t: str) -> str: @@ -46,4 +51,3 @@ def minWindow(self, s: str, t: str) -> str: low += 1 return s[min_low : min_high + 1] if min_high < len(s) else "" - diff --git a/pacific-atlantic-water-flow/KwonNayeon.py b/pacific-atlantic-water-flow/KwonNayeon.py index 7e6e355d2..26410dc33 100644 --- a/pacific-atlantic-water-flow/KwonNayeon.py +++ b/pacific-atlantic-water-flow/KwonNayeon.py @@ -6,18 +6,19 @@ - 0 <= heights[r][c] <= 10^5 Time Complexity: O(m*n) -- 각 셀을 최대 한 번씩만 방문함 +- 각 셀을 최대 한 번만 방문함 Space Complexity: O(m*n) -- visited sets(pacific, atlantic)가 최대 m*n 크기 -- 재귀 호출 스택도 최대 m*n 깊이 가능 +- visited sets(pacific, atlantic)은 최대 m*n 크기 +- 재귀 호출 스택도 최대 m*n까지 가능 풀이방법: -1. Pacific(좌측상단)과 Atlantic(우측하단)의 경계에서 시작함 -2. DFS로 현재 높이보다 높거나 같은 인접 셀로만 이동함 (물은 위 -> 아래로 흐르지만, 거꾸로 접근했으니까) +1. Pacific(좌측상단)과 Atlantic(우측하단)의 경계에서 시작 +2. DFS로 현재 높이보다 높거나 같은 인접 셀로만 이동 (역방향 접근) 3. 각 바다에서 도달 가능한 셀들을 Set에 저장 -4. 두 Set의 교집합이 정답 (양쪽 바다로 모두 흐를 수 있는 지점들) +4. 두 Set의 교집합으로 양쪽 바다 모두 도달 가능한 지점 반환 """ +# 원본 코드 class Solution: def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: if not heights: @@ -51,3 +52,43 @@ def dfs(r, c, visited): dfs(r, cols-1, atlantic) return list(pacific & atlantic) + +# 개선된 코드 +class Solution: + def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: + rows, cols = len(heights), len(heights[0]) + + # 각 바다에서 도달 가능한 위치 저장 + pacific = set() + atlantic = set() + + def dfs(r, c, visited): + # 현재 위치 방문 처리 + visited.add((r, c)) + + # 네 방향 탐색 (오른쪽, 위, 왼쪽, 아래) + for dr, dc in [(0, 1), (-1, 0), (0, -1), (1, 0)]: + new_r, new_c = r + dr, c + dc + + # 경계 체크, 방문x 체크, 높이 체크 + if (0 <= new_r < rows and + 0 <= new_c < cols and + (new_r, new_c) not in visited and + heights[new_r][new_c] >= heights[r][c]): + + dfs(new_r, new_c, visited) + + # Pacific 경계에서 시작 (위 + 왼쪽) + for c in range(cols): + dfs(0, c, pacific) + for r in range(rows): + dfs(r, 0, pacific) + + # Atlantic 경계에서 시작 (아래 + 오른쪽) + for c in range(cols): + dfs(rows-1, c, atlantic) + for r in range(rows): + dfs(r, cols-1, atlantic) + + # 양쪽 바다 모두 도달 가능한 위치 반환 + return [[r, c] for r, c in pacific & atlantic] diff --git a/sum-of-two-integers/KwonNayeon.py b/sum-of-two-integers/KwonNayeon.py index 248fd3470..988ce5e45 100644 --- a/sum-of-two-integers/KwonNayeon.py +++ b/sum-of-two-integers/KwonNayeon.py @@ -3,6 +3,7 @@ - -1000 <= a, b <= 1000 Time Complexity: O(1) +- 비트 자리수, while loop 반복 횟수가 고정되어 있음 Space Complexity: O(1) - 추가 공간을 사용하지 않고 입력받은 변수만 사용 @@ -11,25 +12,28 @@ 1. XOR(^)연산을 통해 캐리를 제외한 각 자리의 합을 구함 2. AND(&)연산 후 왼쪽 시프트(<<)로 다음 자리로 올라갈 캐리를 구함 3. 캐리가 0이 될 때까지 1-2 과정을 반복 + - 캐리는 유한한 비트 범위에서만 존재함 + - 그러므로 계속 왼쪽으로 이동하다 결국 사라짐 + - 캐리가 0이 되면 루프 종료 """ -# Solution 1: 이해하기 쉬운 버전 +# Solution 1 class Solution: def getSum(self, a: int, b: int) -> int: while b: - current_sum = a ^ b - - next_carry = (a & b) << 1 - - a = current_sum - b = next_carry + a, b = a ^ b, (a & b) << 1 return a - -# Solution 2: 최적화 버전 + +# Solution 2: 음수 케이스 처리 class Solution: def getSum(self, a: int, b: int) -> int: - while b: - a, b = a ^ b, (a & b) << 1 + mask = 0xffffffff # 32비트 마스킹 - return a + while b & mask: + # 캐리 없는 덧셈 + 32비트 제한, 캐리 계산 + 32비트 제한 + a, b = (a ^ b) & mask, ((a & b) << 1) & mask + + # a <= 0x7FFFFFFF: 양수일 때 그대로 반환(0x7FFFFFFF = 32비트 최대 양수) + # a > 0x7FFFFFFF: 음수일 때 Python 음수로 변환 + return a if a <= 0x7FFFFFFF else a | (~mask)