From a6b503f74f468aff340f9b5738e5c27f65d150be Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Mon, 15 Dec 2025 22:56:10 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20Valid=20Parentheses=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=92=80=EC=9D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- valid-parentheses/unpo88.py | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 valid-parentheses/unpo88.py diff --git a/valid-parentheses/unpo88.py b/valid-parentheses/unpo88.py new file mode 100644 index 0000000000..3744de5edf --- /dev/null +++ b/valid-parentheses/unpo88.py @@ -0,0 +1,75 @@ +class Solution: + def isValid(self, s: str) -> bool: + stack = [] + pairs = {')': '(', '}': '{', ']': '['} + + for c in s: + if c in pairs: + if not stack or stack.pop() != pairs[c]: + return False + else: + stack.append(c) + + return not stack + + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] Stack으로 직접 비교 +──────────────────────────────────────────────────────────────────────────────── +1. 괄호 판독기 +2. Stack으로 괄호 넣고, 매칭되는것이 나왔을 때, 바로 pop하면서 비교 +3. 하나라도 틀린게 있으면 False +4. 아니면 True + + stack = [] + for c in s: + if c == ')': + if len(stack) == 0 or stack.pop() != '(': + return False + elif c == '}': + if len(stack) == 0 or stack.pop() != '{': + return False + elif c == ']': + if len(stack) == 0 or stack.pop() != '[': + return False + else: + stack.append(c) + + return True if len(stack) == 0 else False + +5. 문제점: 각 닫는 괄호마다 동일한 패턴이 반복됨 +6. 개선 포인트: 딕셔너리로 매핑하면 중복 제거 가능 + +──────────────────────────────────────────────────────────────────────────────── +[2차 시도] 딕셔너리 매핑으로 개선 +──────────────────────────────────────────────────────────────────────────────── +7. pairs = {')': '(', '}': '{', ']': '['} + - 닫는 괄호 → 여는 괄호 매핑 + +8. 개선된 로직: + - c가 닫는 괄호면 (c in pairs) → stack에서 pop해서 매칭 확인 + - c가 여는 괄호면 → stack에 push + + stack = [] + pairs = {')': '(', '}': '{', ']': '['} + + for c in s: + if c in pairs: + if not stack or stack.pop() != pairs[c]: + return False + else: + stack.append(c) + + return not stack + +9. 추가 개선: + - len(stack) == 0 → not stack + - True if ... else False → not stack + +10. 시간복잡도: O(n) - 문자열 한 번 순회 +11. 공간복잡도: O(n) - 최악의 경우 모든 문자가 여는 괄호 +""" From 61954dccbf3bad4bc97412d32083acdba6112645 Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Mon, 15 Dec 2025 23:11:59 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20Container=20With=20Most=20Water=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=ED=92=80=EC=9D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- container-with-most-water/unpo88.py | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 container-with-most-water/unpo88.py diff --git a/container-with-most-water/unpo88.py b/container-with-most-water/unpo88.py new file mode 100644 index 0000000000..6fa3ec6d7b --- /dev/null +++ b/container-with-most-water/unpo88.py @@ -0,0 +1,72 @@ +class Solution: + def maxArea(self, height: List[int]) -> int: + left = 0 + right = len(height) - 1 + max_area = 0 + + while left < right: + min_height = min(height[left], height[right]) + width = right - left + area = min_height * width + + max_area = max(max_area, area) + + if height[left] < height[right]: + left += 1 + else: + right -= 1 + + return max_area + + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] Brute Force로 접근하면? +──────────────────────────────────────────────────────────────────────────────── +1. 모든 (i, j) 쌍에 대해 넓이 계산 +2. height = [1, 8, 6, 2, 5, 4, 8, 3, 7] + - (0, 1): min(1, 8) × 1 = 1 + - (0, 2): min(1, 6) × 2 = 2 + - (0, 8): min(1, 7) × 8 = 8 + - (1, 8): min(8, 7) × 7 = 49 ← 최대 + ... 모든 쌍을 확인해야 함 + +3. 시간복잡도: O(n²) - 시간 초과! +4. 더 효율적인 방법이 필요함 → Two Pointer로 접근 + +──────────────────────────────────────────────────────────────────────────────── +[2차 시도] Two Pointer (양끝에서 좁히기) +──────────────────────────────────────────────────────────────────────────────── +5. 핵심 아이디어: + - 넓이 = min(height[left], height[right]) × (right - left) + - 폭(width)은 양끝에서 시작할 때 최대 + - 포인터를 안쪽으로 이동하면 폭은 무조건 줄어듦 + - 따라서 높이가 커져야 넓이가 커질 가능성이 있음 + +6. 왜 작은 쪽을 이동하는가? + - 작은 쪽이 높이의 병목 (min이 작은 쪽에 의해 결정됨) + - 큰 쪽을 이동하면? → 높이는 그대로거나 작아짐, 폭은 줄어듦 → 손해만 + - 작은 쪽을 이동하면? → 높이가 커질 가능성 있음 → 이득 가능성 + +7. 동작 예시: height = [1, 8, 6, 2, 5, 4, 8, 3, 7] + + Step 1: L=0, R=8 + min(1, 7) × 8 = 8 + height[L]=1 < height[R]=7 → L 이동 + + Step 2: L=1, R=8 + min(8, 7) × 7 = 49 ← 최대! + height[L]=8 > height[R]=7 → R 이동 + + Step 3: L=1, R=7 + min(8, 3) × 6 = 18 + height[L]=8 > height[R]=3 → R 이동 + + ... L과 R이 만날 때까지 반복 + +8. 시간복잡도: O(n) - 한 번만 순회 +9. 공간복잡도: O(1) - 추가 공간 없음 +""" From f83b557465c87a88f173b0a488a6bc816ce7890a Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 20 Dec 2025 08:44:36 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20Design=20Add=20And=20Search=20Words?= =?UTF-8?q?=20Data=20Structure=20=EB=AC=B8=EC=A0=9C=20=ED=92=80=EC=9D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unpo88.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 design-add-and-search-words-data-structure/unpo88.py diff --git a/design-add-and-search-words-data-structure/unpo88.py b/design-add-and-search-words-data-structure/unpo88.py new file mode 100644 index 0000000000..5658d7341f --- /dev/null +++ b/design-add-and-search-words-data-structure/unpo88.py @@ -0,0 +1,99 @@ +class TrieNode: + def __init__(self): + self.children = {} + self.is_end_word = False + + +class WordDictionary: + + def __init__(self): + self.root = TrieNode() + + def addWord(self, word: str) -> None: + node = self.root + for char in word: + if char not in node.children: + node.children[char] = TrieNode() + node = node.children[char] + node.is_end_word = True + + def search(self, word: str) -> bool: + return self._dfs(self.root, word, 0) + + def _dfs(self, node: TrieNode, word: str, index: int) -> bool: + # 단어 끝에 도달하면 is_end_word 확인 + if index == len(word): + return node.is_end_word + + char = word[index] + + if char == '.': + # '.'는 모든 자식 노드 탐색 + for child in node.children.values(): + if self._dfs(child, word, index + 1): + return True + return False + else: + # 일반 문자는 해당 자식으로 이동 + if char not in node.children: + return False + return self._dfs(node.children[char], word, index + 1) + + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] 길이별 그룹화 + 브루트포스 매칭 +──────────────────────────────────────────────────────────────────────────────── +1. 아이디어: 같은 길이 단어끼리 묶고, 하나씩 패턴 비교 + words = { 3: ["bad", "dad", "mad"] } + search(".ad") → 모든 길이 3 단어와 비교 + +2. 문제점: Time Limit Exceeded! + - 같은 길이 단어가 많으면 O(N × L) 반복 + - LeetCode 테스트케이스에서 시간 초과 + +3. 더 효율적인 방법 필요 → Trie로 접근 + +──────────────────────────────────────────────────────────────────────────────── +[2차 시도] Trie (트라이) 자료구조 +──────────────────────────────────────────────────────────────────────────────── +4. Trie 구조 (bad, dad, mad 저장 후): + + root + ├── 'b' → 'a' → 'd' (is_end_word=True) + ├── 'd' → 'a' → 'd' (is_end_word=True) + └── 'm' → 'a' → 'd' (is_end_word=True) + +5. 동작 예시: + + search("bad"): + root → 'b' → 'a' → 'd' → is_end_word=True → True + + search(".ad"): + root → '.' (모든 자식 탐색) + → 'b' → 'a' → 'd' → True (첫 번째에서 찾음!) + + search("b.."): + root → 'b' → '.' (모든 자식) + → 'a' → '.' (모든 자식) + → 'd' → True + +6. 왜 Trie가 더 빠른가? + - 정확한 문자: O(1)로 해당 자식만 탐색 + - '.': 해당 위치에서만 분기, 이후는 다시 좁혀짐 + - 브루트포스: 모든 단어를 처음부터 끝까지 비교 + +7. 시간복잡도: + - addWord: O(L) - L은 단어 길이 + - search: O(L) ~ O(26^m) - m은 '.' 개수 (보통 적음) + +8. 공간복잡도: O(N × L) - 모든 단어의 문자 저장 + +9. 구현 포인트: + - TrieNode 클래스 분리 → 가독성 향상 + - _dfs 재귀로 '.' 처리 → 모든 자식 탐색 + - is_end_word로 단어 끝 표시 → 접두사와 구분 +""" From a48a5a9f8ed30c44835f73d3c2ed29813d8f02a7 Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 20 Dec 2025 08:55:53 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20Longest=20Increasing=20Subsequence?= =?UTF-8?q?=20=EB=AC=B8=EC=A0=9C=20=ED=92=80=EC=9D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- longest-increasing-subsequence/unpo88.py | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 longest-increasing-subsequence/unpo88.py diff --git a/longest-increasing-subsequence/unpo88.py b/longest-increasing-subsequence/unpo88.py new file mode 100644 index 0000000000..2b40793b8d --- /dev/null +++ b/longest-increasing-subsequence/unpo88.py @@ -0,0 +1,64 @@ +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + n = len(nums) + # dp[i] = i번째 원소로 끝나는 LIS의 길이 + dp = [1] * n # 최소 자기 자신 1개 + + for i in range(1, n): + for j in range(i): + # nums[j] < nums[i]이면, j로 끝나는 LIS에 i를 붙일 수 있음 + if nums[j] < nums[i]: + dp[i] = max(dp[i], dp[j] + 1) + + return max(dp) + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] 투포인터? +──────────────────────────────────────────────────────────────────────────────── +1. 처음엔 투포인터로 풀 수 있을 것 같았음 + +2. 근데 생각해보니 Subsequence는 연속된 원소가 아니어도 됨 + - Subarray(부분 배열): 연속 필수 → 투포인터 가능 + - Subsequence(부분 수열): 연속 안 해도 됨 → 투포인터 불가 + +3. 예시: [10, 9, 2, 5, 3, 7, 101, 18] + - LIS = [2, 3, 7, 101] (인덱스 2, 4, 5, 6) + - 연속되지 않은 위치에서 선택해야 함 → DP로 접근 + +──────────────────────────────────────────────────────────────────────────────── +[2차 시도] DP +──────────────────────────────────────────────────────────────────────────────── +4. 정의: dp[i] = nums[i]로 끝나는 LIS의 길이 + +5. 점화식: + dp[i] = max(dp[j] + 1) for all j < i where nums[j] < nums[i] + + 해석: i 이전의 모든 j를 보면서 + nums[j] < nums[i]이면 (증가 조건 만족) + dp[j]에 나를 붙일 수 있으므로 dp[j] + 1 + +6. 동작 예시: nums = [10, 9, 2, 5, 3, 7, 101, 18] + + 초기: dp = [1, 1, 1, 1, 1, 1, 1, 1] + + i=0 (10): 이전 원소 없음 → dp[0] = 1 + i=1 (9): 10 > 9 (X) → dp[1] = 1 + i=2 (2): 10 > 2 (X), 9 > 2 (X) → dp[2] = 1 + i=3 (5): 2 < 5 (O) → dp[3] = dp[2] + 1 = 2 + i=4 (3): 2 < 3 (O) → dp[4] = dp[2] + 1 = 2 + i=5 (7): 2 < 7 (O) → dp[5] = 2 + 5 < 7 (O) → dp[5] = max(2, dp[3]+1) = 3 + 3 < 7 (O) → dp[5] = max(3, dp[4]+1) = 3 + i=6 (101): 7 < 101 (O) → dp[6] = dp[5] + 1 = 4 + i=7 (18): 7 < 18 (O) → dp[7] = dp[5] + 1 = 4 + + 최종: dp = [1, 1, 1, 2, 2, 3, 4, 4] + 답: max(dp) = 4 + +7. 시간복잡도: O(n²) - 이중 for문 +8. 공간복잡도: O(n) - dp 배열 +""" From af8cf15b937542fdc5e51fa64e938bd0fc5157e7 Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 20 Dec 2025 09:03:10 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20Spiral=20Matrix=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=92=80=EC=9D=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spiral-matrix/unpo88.py | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 spiral-matrix/unpo88.py diff --git a/spiral-matrix/unpo88.py b/spiral-matrix/unpo88.py new file mode 100644 index 0000000000..a2d72ddc82 --- /dev/null +++ b/spiral-matrix/unpo88.py @@ -0,0 +1,61 @@ +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + result = [] + + while matrix: + # 1. 맨 위 행을 통째로 추가 + result += matrix.pop(0) + + # 2. 나머지를 90도 반시계 회전 + matrix = list(zip(*matrix))[::-1] + + return result + + +""" +================================================================================ +풀이 과정 +================================================================================ + +[접근] 행 추출 + 회전 +──────────────────────────────────────────────────────────────────────────────── +1. 아이디어: + - 맨 위 행을 떼어내고 + - 남은 행렬을 반시계로 90도 회전 + - 반복하면 자연스럽게 spiral 순서가 됨 + +2. 동작 예시: matrix = [[1,2,3],[4,5,6],[7,8,9]] + + Step 1: pop [1,2,3] → result = [1,2,3] + [[4,5,6], 회전 [[6,9], + [7,8,9]] ────────→ [5,8], + [4,7]] + + Step 2: pop [6,9] → result = [1,2,3,6,9] + [[5,8], 회전 [[8,7], + [4,7]] ────────→ [5,4]] + + Step 3: pop [8,7] → result = [1,2,3,6,9,8,7] + [[5,4]] 회전 [[4], + ────────→ [5]] + + Step 4: pop [4] → result = [1,2,3,6,9,8,7,4] + [[5]] 회전 [[5]] + ────────→ + + Step 5: pop [5] → result = [1,2,3,6,9,8,7,4,5] + [] → 종료 + + 결과: [1,2,3,6,9,8,7,4,5] + +3. 회전 원리: list(zip(*matrix))[::-1] + - zip(*matrix): 열 단위로 묶기 (전치) + - [::-1]: 뒤집기 (반시계 회전) + + [[4,5,6], zip(*) [(4,7), [::-1] [(6,9), + [7,8,9]] ────────→ (5,8), ────────→ (5,8), + (6,9)] (4,7)] + +4. 시간복잡도: O(m × n) - 모든 원소 한 번씩 처리 +5. 공간복잡도: O(m × n) - 회전할 때마다 새 배열 생성 +"""