-
-
Notifications
You must be signed in to change notification settings - Fork 195
[hi-rachel] Week 06 Solutions #1418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5f63a86
ff5dad5
ea0e395
2706b2a
d7bb9cb
9cf961f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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에 없으면 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 트라이를 사용하신 것 같은데 메서드 안에 직접 구현하신 부분이 읽을 때 깔끔하게 느껴졌습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @river20s 달레님 코드 해설을 보고 따라했습니다. 다시 한 번 복습도 해야겠네요 감사합니다! |
||
node[ch] = {"$": False} # 아직 끝이 아님 표시 | ||
node = node[ch] # 자식 노드로 변경 | ||
node["$"] = True # 단어 끝 표시 | ||
|
||
|
||
# TC: O(26^W) => 최악의 경우 영어 알파벳 26개가 각 노드에서 다음 글자가 됨 * 글자수의 비례해서 호출 스택 깊어짐, SC: O(W) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 26은 변하지 않는 상수이기 때문에 여기서 TC를 O(W)로 봐도 무방하지 않을까 생각합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @river20s 그럴 수 있겠네요! 최악의 경우 모든 알파벳의 경우가 나올 수 있다는 점을 기억하고 싶어 적었습니다 :) |
||
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
class WordDictionary { | ||
root: Record<string, any>; | ||
|
||
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<string, any>, 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) | ||
*/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); // 숫자 배열을 펼쳐 여러 숫자를 넘겨줘야 함 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string, string> = { | ||
")": "(", | ||
"]": "[", | ||
"}": "{", | ||
}; | ||
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파이썬은 특히나 TLE가 쉽게 나오는 것 같습니다. TS로 이런 로직을 구현하면 TLE가 나지는 않는지 궁금해집니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@river20s 저는 파이썬에서 타입스크립트로 바꾼다고 시간, 공간 복잡도가 크게 바뀐 경우는 본 적이 없는 것 같습니다. 어떤 로직과 자료구조를 사용하는지가 중요하고 둘 다 인터프리터 언어이기 때문에 큰 차이는 없다고 생각합니다.