From 94d448b1b568ea916743031d2d1be85f7d0aa9f1 Mon Sep 17 00:00:00 2001
From: river20s <river20s@naver.com>
Date: Sun, 4 May 2025 23:58:46 +0900
Subject: [PATCH 1/4] feat: Add Solution to Valid Parentheses #222
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 스택 기반으로 괄호 유효 검사 문제 해결 (LeetCode 20, #222)
- `Stack` 클래스와 내부 헬퍼 메서드로 알고리즘 작성
- 여는 괄호는 push, 닫는 괄호는 pop하여 매칭 여부를 확인하는 식으로 유효성을 검사함
---
 valid-parentheses/river20s.py | 57 +++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 valid-parentheses/river20s.py

diff --git a/valid-parentheses/river20s.py b/valid-parentheses/river20s.py
new file mode 100644
index 000000000..e79d684af
--- /dev/null
+++ b/valid-parentheses/river20s.py
@@ -0,0 +1,57 @@
+class Stack:
+    def __init__(self):
+        self.data = []
+
+    def is_empty(self):
+        return len(self.data) == 0
+    
+    def push(self, element):
+        self.data.append(element)
+
+    def pop(self):
+        if not self.is_empty():
+            return self.data.pop()
+        else:
+            return None
+
+    def peek(self):
+        if not self.is_empty():
+            return self.data[-1]
+        else:
+            return None
+# <<<--- Stack 구현 ---<<<
+# >>>--- 답안 Solution --->>>
+class Solution:
+    # 스택을 활용해 괄호 유효 검사
+    # Time Complexity: O(n)
+    # Space Complexity: O(n)
+    def __init__(self):
+        self._opening_brackets = '([{'
+        self._closing_brackets = ')]}'
+        self._matching_map = {')': '(', ']': '[', '}': '{'}
+
+    def isValid(self, s: str) -> bool:
+        stack = Stack()
+        for char in s:
+            # 여는 괄호라면 스택에 push
+            if self._is_opening(char):
+                stack.push(char)
+            # 닫는 괄호라면
+            # 마지막 열린 괄호와 유형 일치 확인
+            elif self._is_closing(char):
+                if stack.is_empty():
+                    # 스택이 비어 있으면 False 반환
+                    return False
+                last_open_bracket = stack.pop()
+                if not self._is_match(last_open_bracket, char):
+                    return False
+        return stack.is_empty()
+
+    def _is_opening(self, char: str) -> bool:
+        return char in self._opening_brackets
+
+    def _is_closing(self, char: str) -> bool:
+        return char in self._closing_brackets
+
+    def _is_match(self, open_bracket: str, close_bracket: str) -> bool:
+        return self._matching_map.get(close_bracket) == open_bracket 

From 6cdb03507a5e4dd3818ea4d3c893bd438718c727 Mon Sep 17 00:00:00 2001
From: river20s <river20s@naver.com>
Date: Mon, 5 May 2025 23:55:23 +0900
Subject: [PATCH 2/4] feat: Add Solution to Container With Most Water #242
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- LeetCode 11에 대한 답안을 투 포인터를 사용해 구현
- left, right 포인터를 사용하여 컨테이너 넓이들을 계산하고 최댓값을 찾는 방식
- 더 낮은 높이 포인터를 이동하면서 최댓값을 구함
- TC: O(n), SC: O(1)
---
 container-with-most-water/river20s.py | 34 +++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 container-with-most-water/river20s.py

diff --git a/container-with-most-water/river20s.py b/container-with-most-water/river20s.py
new file mode 100644
index 000000000..5b48122bc
--- /dev/null
+++ b/container-with-most-water/river20s.py
@@ -0,0 +1,34 @@
+class Solution:
+    def maxArea(self, height: List[int]) -> int:
+            # 시작과 끝 선분을 포인터로 두고
+            # 두 선분으로 만들 수 있는 넓이:
+            # 너비 = right - left
+            # 높이 = min(height[left], height[right])
+            # 넓이 = 너비 * 높이의 최대 값을 구하는 문제
+            # Time Complexity: O(n)
+            # 두 포인터가 한 칸씩만 이동하며 서로 만날 때 루프 종료
+            # Space Complexity: O(1)
+            n = len(height)
+            left = 0 # 왼쪽(시작) 포인터
+            right = n - 1 # 오른쪽(끝) 포인터
+            max_area = 0
+
+            while left < right:
+                # 현재 높이는 두 직선 중 낮은 쪽
+                current_height = min(height[left], height[right])
+                # 현재 너비는 오른쪽 점과 왼쪽 점의 차
+                current_width = right - left
+                # 넓이 = 높이 * 너비 
+                current_area = current_height * current_width
+                # 최대 넓이라면 업데이트
+                max_area = max(max_area, current_area)
+                # 포인터 이동 후 탐색
+                # 둘 중 더 낮은 쪽의 포인터를 안으로 움직여서 넓이 계산 
+                # 더 큰 넓이를 찾는 것이 목표, 포인터를 안으로 움직이면 너비는 무조건 감소
+                # 높이라도 증가할 가능성이 있어야 하므로 기존 낮은 높이가 늘어날 가능성에 배팅
+                # 둘이 같다면 오른쪽 이동(아무쪽이나 가능)
+                if height[left] < height[right]:
+                    left += 1
+                else:
+                    right -= 1
+            return max_area

From 3272976a77823644e6fd5fed59fba6a608b1c311 Mon Sep 17 00:00:00 2001
From: river20s <river20s@naver.com>
Date: Sat, 10 May 2025 00:39:24 +0900
Subject: [PATCH 3/4] feat: Add Solution to #257 leetcode 211. Design Add and
 Search Words Data Structure

---
 .../river20s.py                               | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 design-add-and-search-words-data-structure/river20s.py

diff --git a/design-add-and-search-words-data-structure/river20s.py b/design-add-and-search-words-data-structure/river20s.py
new file mode 100644
index 000000000..03d96c453
--- /dev/null
+++ b/design-add-and-search-words-data-structure/river20s.py
@@ -0,0 +1,57 @@
+class TrieNode:
+    def __init__(self):
+        self.children = {}
+        self.isEndOfWord = False
+# <<<--- 트라이 자료 구조 사용을 위한 구현 ---<<<
+# >>>----------------- 답안 ---------------->>>
+class WordDictionary:
+
+    def __init__(self):
+        self.root = TrieNode()
+
+    def addWord(self, word: str) -> None:
+        # 추가: word를 자료 구조에 추가
+        # 일반적인 트라이 삽입 로직과 같음
+        # Time Complexity: O(L),   Space Complexity: O(L)
+        # L은 추가할 단어 길이
+        currentNode = self.root
+        for char in word:
+            if char not in currentNode.children:
+                currentNode.children[char] = TrieNode()
+            currentNode = currentNode.children[char]
+        currentNode.isEndOfWord = True
+
+    def search(self, word: str) -> bool:
+        # 검색: 자료 구조에 word와 일치하는 문자열이 있으면 True
+        # 그렇지 않으면 False 반환
+        # 와일드카드 '.'는 어떤 글자도 될 수 있음
+        # Time Coplexity: O(M),    Space Complexity: O(M)
+        # M은 검색할 단어 길이
+
+        def _dfs_search(node: TrieNode, index_in_search_word: int) -> bool:
+            # 재귀를 위한 헬퍼 함수
+            # --- 베이스 케이스 ---
+            # 검색에 끝에 도달했을 때 트라이 노드가 마지막 노드라면 검색 성공
+            if index_in_search_word == len(word):
+                return node.isEndOfWord
+            
+            # 트라이의 현재 노드와 비교해야할 검색어 word의 문자
+            char_to_match = word[index_in_search_word]
+
+            # 현재 문자가 알파벳인 경우
+            if char_to_match != ".":
+                # 자식 노드에 비교할 문자가 없는 경우
+                if char_to_match not in node.children:
+                    return False
+                # 다음 문자에 대한 재귀 호출
+                return _dfs_search(node.children[char_to_match], index_in_search_word + 1)
+
+            # 현재 문자가 '.'인 경우
+            else:
+                # 가능한 모든 자식 노드에 대해 재귀 호출
+                for child_node in node.children.values():
+                    if _dfs_search(child_node, index_in_search_word + 1):
+                        return True # 하나라도 성공하면 True
+                return False
+        # 최초 재귀 호출
+        return _dfs_search(self.root, 0)

From ed58d52edcda42909dc6158563716c83d1fb91e7 Mon Sep 17 00:00:00 2001
From: river20s <river20s@naver.com>
Date: Sat, 10 May 2025 23:48:36 +0900
Subject: [PATCH 4/4] feat: Add Solution to #272

---
 longest-increasing-subsequence/river20s.py | 82 ++++++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100644 longest-increasing-subsequence/river20s.py

diff --git a/longest-increasing-subsequence/river20s.py b/longest-increasing-subsequence/river20s.py
new file mode 100644
index 000000000..2d175a84c
--- /dev/null
+++ b/longest-increasing-subsequence/river20s.py
@@ -0,0 +1,82 @@
+# import bisect
+from typing import List
+class Solution():
+    def _my_bisect_left(self, a: List[int], x:int) -> int:
+        """
+        정렬된 리스트 a에 x를 삽입할 가장 왼쪽 인덱스 반환
+        """
+        # x보다 크거나 같은 첫 번째 원소의 인덱스 찾아주기
+        # bisect.bisect_left(a, x)를 직접 구현
+        low = 0 # 시작 인덱스, 0으로 초기화
+        high = len(a) # 끝 인덱스, 리스트의 길이로 초기화
+
+        while low < high:
+            mid = low + (high - low) // 2
+            if a[mid] < x:
+                low = mid + 1
+            else: # a[mid] >= x
+                high = mid
+        return low # low가 삽입 지점
+
+    def lengthOfLIS(self, nums: List[int]) -> int:
+        """
+        """
+        if not nums: # nums가 빈 배열이라면 먼저 0 반환
+            return 0
+        tails = [] # 각 길이의 LIS를 만들 수 있는 가장 작은 마지막 값을 저장할 리스트
+        for num in nums:
+            # num을 삽입할 수 있는 가장 왼쪽 위치 찾기
+            # -> tails 리스트에서 num 보다 크거나 같은 첫 번째 요소 인덱스 찾기
+            # -> 만약 tails 모든 값보다 크다면 추가할 위치를 반환함
+            idx = self._my_bisect_left(tails, num)
+
+            # 만약 idx가 tails 현재 길이와 같다면
+            # -> num이 tails의 모든 요소보다 크다
+            # -> tails에 num "추가"
+            if idx == len(tails):
+                tails.append(num)
+            # 만약 idx가 tails 현재 길이보다 작다면
+            # -> 가능성 발견, tails[idx]를 num으로 "교체"
+            # -> LIS를 개선
+            else:
+                tails[idx] = num
+
+            # 모든 숫자 처리 후, tails 길이가 LIS길이가 됨
+        return len(tails)
+
+
+    """
+    def lengthOfLIS_bruteforce(self, nums: List[int]) -> int:
+        # 초기에 구현한 탐욕 기반 알고리즘
+        # --- 복잡도 ---
+        # Time Complexity: O(n^2)
+        # Space Complexity: O(1)
+        # --- 한계 ---
+        # [지금 당장 가장 좋아보이는 선택이 전체적으로는 최적이 아닌 경우]        
+        # nums = [0,1,0,3,2,3] 같은 경우 답을 찾지 못함 (예측 값: 4, 실제 값: 3)
+        # 각 요소에서 시작하여 탐욕적으로 다음 큰 수를 찾아가는 알고리즘으로 모든 LIS를 찾을 수 없음
+        # 각 시작점에서 하나의 증가 부분 수열을 찾는 것, 항상 긴 것을 보장할 수 없음
+        # 이미 찾은 LIS 뒤에 더 긴 LIS가 있다면 찾을 수 없음
+        n = len(nums) # n은 nums의 길이(개수)
+        if n == 0:    # 길이가 0이면 우선적으로 0을 반환
+            return 0
+        
+        max_overall_length = 0 # 전체 최대 길이, 0으로 초기화
+
+        for idx_i in range(n): # 0부터 n까지 순회하는 외부 루프
+            current_length = 1 # 현재(index: idx_i) 부분 수열의 길이를 1로 초기화
+            last_element_in_subsequence = nums[idx_i] # 부분 수열의 마지막 숫자를 현재 숫자로 초기화
+
+            for idx_j in range(idx_i + 1, n): # 다음 요소의 인덱스를 idx_j로 하여 끝까지 순회
+                # 만약 다음 요소가 부분 수열의 마지막 숫자보다 크다면
+                if nums[idx_j] > last_element_in_subsequence:
+                    # 마지막 숫자를 다음 요소로 갱신
+                    last_element_in_subsequence = nums[idx_j]
+                    # 현재 부분 수열의 길이를 1 증가
+                    current_length += 1
+
+            # 현재 부분 수열 길이가 전체 부분 수열 길이보다 큰 경우 갱신
+            max_overall_length = max(max_overall_length, current_length)
+
+        return max_overall_length
+        """