diff --git a/container-with-most-water/hi-rachel.py b/container-with-most-water/hi-rachel.py new file mode 100644 index 000000000..08edbae09 --- /dev/null +++ b/container-with-most-water/hi-rachel.py @@ -0,0 +1,36 @@ +""" +너비: e - s +높이: min(height[s], height[e]) + +넓이 = e - s * min(height[s], height[e]) +""" + +# TC: O(N^2), SC: O(1) +# 모든 경우의 수를 따져 가장 넓이가 크게 나오는 경우를 찾는 풀이 -> Time Limit Exceeded +class Solution: + def maxArea(self, height: List[int]) -> int: + max_area = 0 + + for s in range(len(height) - 1): + for e in range(s + 1, len(height)): + area = (e - s) * min(height[s], height[e]) + max_area = max(area, max_area) + return max_area + + +# 투 포인터 풀이 +# TC: O(N), SC: O(1) +class Solution: + def maxArea(self, height: List[int]) -> int: + max_area = 0 + s, e = 0, len(height) - 1 + while s < e: + area = (e - s) * min(height[s], height[e]) + max_area = max(area, max_area) + if height[s] < height[e]: + s += 1 + else: + e -= 1 + + return max_area + diff --git a/container-with-most-water/hi-rachel.ts b/container-with-most-water/hi-rachel.ts new file mode 100644 index 000000000..b80ec7b86 --- /dev/null +++ b/container-with-most-water/hi-rachel.ts @@ -0,0 +1,23 @@ +/** +너비: (e - s) +높이: Math.min(height[s], height[e]) + +넓이: (e - s) * Math.min(height[s], height[e]) + +# TC: O(N), SC: O(1) +*/ + +function maxArea(height: number[]): number { + let maxArea = 0; + let s = 0, + e = height.length - 1; + while (s < e) { + maxArea = Math.max((e - s) * Math.min(height[s], height[e]), maxArea); + if (height[s] > height[e]) { + e -= 1; + } else { + s += 1; + } + } + return maxArea; +} diff --git a/design-add-and-search-words-data-structure/hi-rachel.py b/design-add-and-search-words-data-structure/hi-rachel.py new file mode 100644 index 000000000..55a0b50ae --- /dev/null +++ b/design-add-and-search-words-data-structure/hi-rachel.py @@ -0,0 +1,38 @@ +class WordDictionary: + + def __init__(self): + self.root = {"$": True} + + + # TC: O(W), SC: O(W) + def addWord(self, word: str) -> None: + node = self.root + for ch in word: + if ch not in node: # 글자가 node에 없으면 + node[ch] = {"$": False} # 아직 끝이 아님 표시 + node = node[ch] # 자식 노드로 변경 + node["$"] = True # 단어 끝 표시 + + + # TC: O(26^W) => 최악의 경우 영어 알파벳 26개가 각 노드에서 다음 글자가 됨 * 글자수의 비례해서 호출 스택 깊어짐, SC: O(W) + def search(self, word: str) -> bool: + def dfs(node, idx): + if idx == len(word): + return node["$"] + + ch = word[idx] + if ch in node: + return dfs(node[ch], idx + 1) + if ch == ".": # 글자가 .이라면 + # 노드의 모든 자식 노드 호출 (어느 경로에서 글자가 일치할지 모르기 때문) + if any(dfs(node[k], idx + 1) for k in node if k != '$'): + return True + return False + + return dfs(self.root, 0) # 최상위 노드, 최초 idx + + +# Your WordDictionary object will be instantiated and called as such: +# obj = WordDictionary() +# obj.addWord(word) +# param_2 = obj.search(word) diff --git a/design-add-and-search-words-data-structure/hi-rachel.ts b/design-add-and-search-words-data-structure/hi-rachel.ts new file mode 100644 index 000000000..397036a58 --- /dev/null +++ b/design-add-and-search-words-data-structure/hi-rachel.ts @@ -0,0 +1,48 @@ +class WordDictionary { + root: Record; + + constructor() { + this.root = { $: true }; + } + + addWord(word: string): void { + let node = this.root; + for (const ch of word) { + if (!(ch in node)) { + node[ch] = { $: false }; + } + node = node[ch]; + } + node["$"] = true; + } + + search(word: string): boolean { + const dfs = (node: Record, idx: number): boolean => { + if (idx === word.length) return node["$"]; + + const ch = word[idx]; + if (ch === ".") { + for (const key in node) { + if (key !== "$" && dfs(node[key], idx + 1)) { + return true; + } + } + return false; + } + + if (ch in node) { + return dfs(node[ch], idx + 1); + } + + return false; + }; + return dfs(this.root, 0); + } +} + +/** + * Your WordDictionary object will be instantiated and called as such: + * var obj = new WordDictionary() + * obj.addWord(word) + * var param_2 = obj.search(word) + */ diff --git a/longest-increasing-subsequence/hi-rachel.py b/longest-increasing-subsequence/hi-rachel.py new file mode 100644 index 000000000..c22ee24a0 --- /dev/null +++ b/longest-increasing-subsequence/hi-rachel.py @@ -0,0 +1,53 @@ +""" +주어진 정수 배열 nums에서 가장 긴 증가하는 부분 수열의 길이를 구하는 문제 + +동적 계획법, 이중 포문 풀이 - TC: O(n^2), SC: O(n) + +dp[i]: nums[i]로 끝나는 가장 긴 증가 수열의 길이. 초기값은 모두 1 (자기 자신만 있을 때) + +오름차순 부분 수열 -> 앞에 있는 수가 뒤에 있는 수보다 작아야 함. +""" + +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + n = len(nums) + dp = [1] * n + for i in range(n): + for j in range(i): + if nums[j] < nums[i]: # 오름차순 조건 만족한다면 + dp[i] = max(dp[i], dp[j] + 1) # 현재와 nums[j] 뒤에 nums[i]를 붙인 수열 중 더 긴 길이 선택 + return max(dp) # 가장 긴 증가 수열의 길이 + + +""" +주어진 정수 배열 nums에서 가장 긴 증가하는 부분 수열의 길이를 구하는 문제 + +이분 탐색 풀이 - TC: O(n log n), SC: O(n) + +# bisect란? +bisect는 Python의 표준 라이브러리로 정렬된 리스트에 이진 탐색으로 원소를 삽입할 위치를 찾는 모듈. + +bisect_left(arr, x): +- x를 삽입할 가장 왼쪽 위치를 반환 += 정렬된 오름차순 배열 arr에서 x를 끼워 넣을 수 있는 가장 왼쪽 위치 (index) 찾아줌 +- 같은 값이 있어도 그 앞에 끼워 넣음 + +bisect_right(arr, x): +- 오른쪽 경계 = 오름차순 정렬된 배열 arr에서, 값 x를 삽입할 가장 오른쪽 위치 +- 같은 값이 있으면 그 뒤에 끼워 넣음 + +해당 문제는 Strictly Increasing 수열이므로 같은 숫자를 허용 x +-> 같은 값이 들어오면 기존 값을 대체해야지, 그 뒤에 추가하면 안되므로 bisect_left 사용한다. +""" +import bisect + +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + tail = [] # 각 길이별 증가 수열의 마지막 값(가장 작은 값)을 저장 + for num in nums: + idx = bisect.bisect_left(tail, num) + if idx == len(tail): # 반환된 idx가 tail의 끝 부분이라면 현재 수열 끝에 추가될 수 있다는 뜻, 즉 num이 tail 안의 모든 값보다 큼 + tail.append(num) # 수열 길이 늘림 + else: + tail[idx] = num # 더 작은 값으로 대체 + return len(tail) diff --git a/longest-increasing-subsequence/hi-rachel.ts b/longest-increasing-subsequence/hi-rachel.ts new file mode 100644 index 000000000..52b45f3f8 --- /dev/null +++ b/longest-increasing-subsequence/hi-rachel.ts @@ -0,0 +1,21 @@ +/** + * 주어진 정수 배열 nums에서 가장 긴 증가하는 부분 수열의 길이를 구하는 문제 + * + * new Array(n).fill(1) -> 길이가 n인 배열을 만들어, 모든 요소를 1로 채움 + * + * TC: O(n^2), SC: O(n) + */ + +function lengthOfLIS(nums: number[]): number { + const n = nums.length; + const dp = new Array(n).fill(1); // In Python => [1] * n + + for (let i = 0; i < n; i++) { + for (let j = 0; j < i; j++) { + if (nums[j] < nums[i]) { + dp[i] = Math.max(dp[j] + 1, dp[i]); + } + } + } + return Math.max(...dp); // 숫자 배열을 펼쳐 여러 숫자를 넘겨줘야 함 +} diff --git a/spiral-matrix/hi-rachel.py b/spiral-matrix/hi-rachel.py new file mode 100644 index 000000000..43528e9a2 --- /dev/null +++ b/spiral-matrix/hi-rachel.py @@ -0,0 +1,88 @@ +""" +- 위쪽 행을 순회한 후, 상단 경계를 1 증가 +- 오른쪽 열을 순회한 후, 우측 경계를 1 감소 +- 아래쪽 행을 순회한 후, 하단 경계를 1 감소 +- 왼쪽 열을 순회한 후, 좌측 경계를 1 증가 + +탈출 조건 +1. 위쪽 행 순회를 마치고 상단 인덱스가 하단 인덱스보다 커지면 +2. 오른쪽 열 순회를 마치고, 우측 인덱스가 좌측 인덱스보다 작아지면 + +TC: O(m * n) => 행렬의 저장된 모든 원소를 딱 1번씩만 접근, SC: O(1) +""" + +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + top, bottom = 0, len(matrix) - 1 # 행의 수 len(maxtrix) + left, right = 0, len(matrix[0]) - 1 # 열의 수 len(maxtrix[0]) + + output = [] + + while top <= bottom and left <= right: + # 위쪽 행 순회 + for c in range(left, right + 1): + output.append(matrix[top][c]) + top += 1 + + if top > bottom: + break + + # 오른쪽 열 순회 + for r in range(top, bottom + 1): + output.append(matrix[r][right]) + right -= 1 + + if left > right: + break + + # 아래쪽 행 순회 + for c in range(right, left - 1, -1): + output.append(matrix[bottom][c]) + bottom -= 1 + + # 왼쪽 열 순회 + for r in range(bottom, top - 1, -1): + output.append(matrix[r][left]) + left += 1 + + return output + +# 2개의 포인터만 사용하는 풀이 +""" +- 위쪽 행을 순회한 후, 열 인덱스를 1씩 증가 +- 오른쪽 열을 순회한 후, 행 인덱스를 1씩 증가 +- 아래쪽 행을 순회한 후, 열 인덱스를 1씩 감소 +- 왼쪽 열을 순회한 후, 행 인덱스를 1씩 감소 + +인덱스 +시작: (0, 0) => (행, 열) + +탈출 조건 +1. 위쪽 행 순회를 마치고 상단 인덱스가 하단 인덱스보다 커지면 +2. 오른쪽 열 순회를 마치고, 우측 인덱스가 좌측 인덱스보다 작아지면 +""" + +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + n_rows, n_cols = len(matrix), len(matrix[0]) + row, col = 0, -1 + direction = 1 + + output = [] + + # 남아있는 열과 행의 개수가 0 보다 큰 동안 + while 0 < n_rows and 0 < n_cols: + for _ in range(n_cols): + col += direction + output.append(matrix[row][col]) + n_rows -= 1 + + for _ in range(n_rows): + row += direction + output.append(matrix[row][col]) + n_cols -= 1 + + direction *= -1 + + return output + diff --git a/valid-parentheses/hi-rachel.ts b/valid-parentheses/hi-rachel.ts new file mode 100644 index 000000000..7c89a8ee3 --- /dev/null +++ b/valid-parentheses/hi-rachel.ts @@ -0,0 +1,72 @@ +// TC: O(N^2), SC: O(N) +// function isValid(s: string): boolean { +// let prevLength = -1; + +// while (s.length !== prevLength) { +// prevLength = s.length; +// s = s.replace("()", "").replace("[]", "").replace("{}", ""); +// } + +// return s.length === 0; +// } + +/** + * STACK 풀이 + * TC: O(N), SC: O(N) + * 스택 방식 작동 순서 +* for (const char of s) { + if (여는 괄호) { + 스택에 push + } else { + 스택에서 pop한 값이 char의 짝이 아니면 return false + } +} + */ +function isValid(s: string): boolean { + const stack: string[] = []; + const parentheseMap: Record = { + ")": "(", + "]": "[", + "}": "{", + }; + + for (const char of s) { + if (["(", "[", "{"].includes(char)) { + stack.push(char); + } else { + if (stack.pop() !== parentheseMap[char]) return false; + } + } + return stack.length === 0; +} + +/** + * Python과 JS에서의 pop() 메서드 차이 + * JS 같은 로직을 Python으로 바꾸면 Python에서만 다음과 같은 테스트 케이스 오류가 발생 + * TEST CASE: "]", "){", ")(){}" + * + * [원인] + * if (stack.pop() !== parentheseMap[char]) return false; 비교시 + * JavaScript에서는 빈 스택 pop() -> undefined 반환으로 비교 가능! + * Python에서는 빈 스택 pop() -> IndexError -> 오류 발생 + * + * [해결책] - 예외 처리 필요 + * pop 전에 not stack 체크 꼭 해주기 + * not stack -> 스택이 비어 있다면 잘못된 닫는 괄호 먼저 나온 경우 + */ + +// PY 풀이 +// class Solution: +// def isValid(self, s: str) -> bool: +// parentheseMap = {")" : "(", "]": "[", "}": "{"} +// stack = [] +// open_parenthese = "([{" + +// for char in s: +// if char in open_parenthese: +// stack.append(char) +// else: +// if (not stack or stack.pop() != parentheseMap[char]): +// return False + +// return len(stack) == 0