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 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) 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 + """ 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