Skip to content

Commit 23bcea3

Browse files
authored
Merge pull request #1418 from hi-rachel/main
[hi-rachel] Week 06 Solutions
2 parents 098cfe9 + 9cf961f commit 23bcea3

File tree

8 files changed

+379
-0
lines changed

8 files changed

+379
-0
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
너비: e - s
3+
높이: min(height[s], height[e])
4+
5+
넓이 = e - s * min(height[s], height[e])
6+
"""
7+
8+
# TC: O(N^2), SC: O(1)
9+
# 모든 경우의 수를 따져 가장 넓이가 크게 나오는 경우를 찾는 풀이 -> Time Limit Exceeded
10+
class Solution:
11+
def maxArea(self, height: List[int]) -> int:
12+
max_area = 0
13+
14+
for s in range(len(height) - 1):
15+
for e in range(s + 1, len(height)):
16+
area = (e - s) * min(height[s], height[e])
17+
max_area = max(area, max_area)
18+
return max_area
19+
20+
21+
# 투 포인터 풀이
22+
# TC: O(N), SC: O(1)
23+
class Solution:
24+
def maxArea(self, height: List[int]) -> int:
25+
max_area = 0
26+
s, e = 0, len(height) - 1
27+
while s < e:
28+
area = (e - s) * min(height[s], height[e])
29+
max_area = max(area, max_area)
30+
if height[s] < height[e]:
31+
s += 1
32+
else:
33+
e -= 1
34+
35+
return max_area
36+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
너비: (e - s)
3+
높이: Math.min(height[s], height[e])
4+
5+
넓이: (e - s) * Math.min(height[s], height[e])
6+
7+
# TC: O(N), SC: O(1)
8+
*/
9+
10+
function maxArea(height: number[]): number {
11+
let maxArea = 0;
12+
let s = 0,
13+
e = height.length - 1;
14+
while (s < e) {
15+
maxArea = Math.max((e - s) * Math.min(height[s], height[e]), maxArea);
16+
if (height[s] > height[e]) {
17+
e -= 1;
18+
} else {
19+
s += 1;
20+
}
21+
}
22+
return maxArea;
23+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
class WordDictionary:
2+
3+
def __init__(self):
4+
self.root = {"$": True}
5+
6+
7+
# TC: O(W), SC: O(W)
8+
def addWord(self, word: str) -> None:
9+
node = self.root
10+
for ch in word:
11+
if ch not in node: # 글자가 node에 없으면
12+
node[ch] = {"$": False} # 아직 끝이 아님 표시
13+
node = node[ch] # 자식 노드로 변경
14+
node["$"] = True # 단어 끝 표시
15+
16+
17+
# TC: O(26^W) => 최악의 경우 영어 알파벳 26개가 각 노드에서 다음 글자가 됨 * 글자수의 비례해서 호출 스택 깊어짐, SC: O(W)
18+
def search(self, word: str) -> bool:
19+
def dfs(node, idx):
20+
if idx == len(word):
21+
return node["$"]
22+
23+
ch = word[idx]
24+
if ch in node:
25+
return dfs(node[ch], idx + 1)
26+
if ch == ".": # 글자가 .이라면
27+
# 노드의 모든 자식 노드 호출 (어느 경로에서 글자가 일치할지 모르기 때문)
28+
if any(dfs(node[k], idx + 1) for k in node if k != '$'):
29+
return True
30+
return False
31+
32+
return dfs(self.root, 0) # 최상위 노드, 최초 idx
33+
34+
35+
# Your WordDictionary object will be instantiated and called as such:
36+
# obj = WordDictionary()
37+
# obj.addWord(word)
38+
# param_2 = obj.search(word)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class WordDictionary {
2+
root: Record<string, any>;
3+
4+
constructor() {
5+
this.root = { $: true };
6+
}
7+
8+
addWord(word: string): void {
9+
let node = this.root;
10+
for (const ch of word) {
11+
if (!(ch in node)) {
12+
node[ch] = { $: false };
13+
}
14+
node = node[ch];
15+
}
16+
node["$"] = true;
17+
}
18+
19+
search(word: string): boolean {
20+
const dfs = (node: Record<string, any>, idx: number): boolean => {
21+
if (idx === word.length) return node["$"];
22+
23+
const ch = word[idx];
24+
if (ch === ".") {
25+
for (const key in node) {
26+
if (key !== "$" && dfs(node[key], idx + 1)) {
27+
return true;
28+
}
29+
}
30+
return false;
31+
}
32+
33+
if (ch in node) {
34+
return dfs(node[ch], idx + 1);
35+
}
36+
37+
return false;
38+
};
39+
return dfs(this.root, 0);
40+
}
41+
}
42+
43+
/**
44+
* Your WordDictionary object will be instantiated and called as such:
45+
* var obj = new WordDictionary()
46+
* obj.addWord(word)
47+
* var param_2 = obj.search(word)
48+
*/
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""
2+
주어진 정수 배열 nums에서 가장 긴 증가하는 부분 수열의 길이를 구하는 문제
3+
4+
동적 계획법, 이중 포문 풀이 - TC: O(n^2), SC: O(n)
5+
6+
dp[i]: nums[i]로 끝나는 가장 긴 증가 수열의 길이. 초기값은 모두 1 (자기 자신만 있을 때)
7+
8+
오름차순 부분 수열 -> 앞에 있는 수가 뒤에 있는 수보다 작아야 함.
9+
"""
10+
11+
class Solution:
12+
def lengthOfLIS(self, nums: List[int]) -> int:
13+
n = len(nums)
14+
dp = [1] * n
15+
for i in range(n):
16+
for j in range(i):
17+
if nums[j] < nums[i]: # 오름차순 조건 만족한다면
18+
dp[i] = max(dp[i], dp[j] + 1) # 현재와 nums[j] 뒤에 nums[i]를 붙인 수열 중 더 긴 길이 선택
19+
return max(dp) # 가장 긴 증가 수열의 길이
20+
21+
22+
"""
23+
주어진 정수 배열 nums에서 가장 긴 증가하는 부분 수열의 길이를 구하는 문제
24+
25+
이분 탐색 풀이 - TC: O(n log n), SC: O(n)
26+
27+
# bisect란?
28+
bisect는 Python의 표준 라이브러리로 정렬된 리스트에 이진 탐색으로 원소를 삽입할 위치를 찾는 모듈.
29+
30+
bisect_left(arr, x):
31+
- x를 삽입할 가장 왼쪽 위치를 반환
32+
= 정렬된 오름차순 배열 arr에서 x를 끼워 넣을 수 있는 가장 왼쪽 위치 (index) 찾아줌
33+
- 같은 값이 있어도 그 앞에 끼워 넣음
34+
35+
bisect_right(arr, x):
36+
- 오른쪽 경계 = 오름차순 정렬된 배열 arr에서, 값 x를 삽입할 가장 오른쪽 위치
37+
- 같은 값이 있으면 그 뒤에 끼워 넣음
38+
39+
해당 문제는 Strictly Increasing 수열이므로 같은 숫자를 허용 x
40+
-> 같은 값이 들어오면 기존 값을 대체해야지, 그 뒤에 추가하면 안되므로 bisect_left 사용한다.
41+
"""
42+
import bisect
43+
44+
class Solution:
45+
def lengthOfLIS(self, nums: List[int]) -> int:
46+
tail = [] # 각 길이별 증가 수열의 마지막 값(가장 작은 값)을 저장
47+
for num in nums:
48+
idx = bisect.bisect_left(tail, num)
49+
if idx == len(tail): # 반환된 idx가 tail의 끝 부분이라면 현재 수열 끝에 추가될 수 있다는 뜻, 즉 num이 tail 안의 모든 값보다 큼
50+
tail.append(num) # 수열 길이 늘림
51+
else:
52+
tail[idx] = num # 더 작은 값으로 대체
53+
return len(tail)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* 주어진 정수 배열 nums에서 가장 긴 증가하는 부분 수열의 길이를 구하는 문제
3+
*
4+
* new Array(n).fill(1) -> 길이가 n인 배열을 만들어, 모든 요소를 1로 채움
5+
*
6+
* TC: O(n^2), SC: O(n)
7+
*/
8+
9+
function lengthOfLIS(nums: number[]): number {
10+
const n = nums.length;
11+
const dp = new Array(n).fill(1); // In Python => [1] * n
12+
13+
for (let i = 0; i < n; i++) {
14+
for (let j = 0; j < i; j++) {
15+
if (nums[j] < nums[i]) {
16+
dp[i] = Math.max(dp[j] + 1, dp[i]);
17+
}
18+
}
19+
}
20+
return Math.max(...dp); // 숫자 배열을 펼쳐 여러 숫자를 넘겨줘야 함
21+
}

spiral-matrix/hi-rachel.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
- 위쪽 행을 순회한 후, 상단 경계를 1 증가
3+
- 오른쪽 열을 순회한 후, 우측 경계를 1 감소
4+
- 아래쪽 행을 순회한 후, 하단 경계를 1 감소
5+
- 왼쪽 열을 순회한 후, 좌측 경계를 1 증가
6+
7+
탈출 조건
8+
1. 위쪽 행 순회를 마치고 상단 인덱스가 하단 인덱스보다 커지면
9+
2. 오른쪽 열 순회를 마치고, 우측 인덱스가 좌측 인덱스보다 작아지면
10+
11+
TC: O(m * n) => 행렬의 저장된 모든 원소를 딱 1번씩만 접근, SC: O(1)
12+
"""
13+
14+
class Solution:
15+
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
16+
top, bottom = 0, len(matrix) - 1 # 행의 수 len(maxtrix)
17+
left, right = 0, len(matrix[0]) - 1 # 열의 수 len(maxtrix[0])
18+
19+
output = []
20+
21+
while top <= bottom and left <= right:
22+
# 위쪽 행 순회
23+
for c in range(left, right + 1):
24+
output.append(matrix[top][c])
25+
top += 1
26+
27+
if top > bottom:
28+
break
29+
30+
# 오른쪽 열 순회
31+
for r in range(top, bottom + 1):
32+
output.append(matrix[r][right])
33+
right -= 1
34+
35+
if left > right:
36+
break
37+
38+
# 아래쪽 행 순회
39+
for c in range(right, left - 1, -1):
40+
output.append(matrix[bottom][c])
41+
bottom -= 1
42+
43+
# 왼쪽 열 순회
44+
for r in range(bottom, top - 1, -1):
45+
output.append(matrix[r][left])
46+
left += 1
47+
48+
return output
49+
50+
# 2개의 포인터만 사용하는 풀이
51+
"""
52+
- 위쪽 행을 순회한 후, 열 인덱스를 1씩 증가
53+
- 오른쪽 열을 순회한 후, 행 인덱스를 1씩 증가
54+
- 아래쪽 행을 순회한 후, 열 인덱스를 1씩 감소
55+
- 왼쪽 열을 순회한 후, 행 인덱스를 1씩 감소
56+
57+
인덱스
58+
시작: (0, 0) => (행, 열)
59+
60+
탈출 조건
61+
1. 위쪽 행 순회를 마치고 상단 인덱스가 하단 인덱스보다 커지면
62+
2. 오른쪽 열 순회를 마치고, 우측 인덱스가 좌측 인덱스보다 작아지면
63+
"""
64+
65+
class Solution:
66+
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
67+
n_rows, n_cols = len(matrix), len(matrix[0])
68+
row, col = 0, -1
69+
direction = 1
70+
71+
output = []
72+
73+
# 남아있는 열과 행의 개수가 0 보다 큰 동안
74+
while 0 < n_rows and 0 < n_cols:
75+
for _ in range(n_cols):
76+
col += direction
77+
output.append(matrix[row][col])
78+
n_rows -= 1
79+
80+
for _ in range(n_rows):
81+
row += direction
82+
output.append(matrix[row][col])
83+
n_cols -= 1
84+
85+
direction *= -1
86+
87+
return output
88+

valid-parentheses/hi-rachel.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// TC: O(N^2), SC: O(N)
2+
// function isValid(s: string): boolean {
3+
// let prevLength = -1;
4+
5+
// while (s.length !== prevLength) {
6+
// prevLength = s.length;
7+
// s = s.replace("()", "").replace("[]", "").replace("{}", "");
8+
// }
9+
10+
// return s.length === 0;
11+
// }
12+
13+
/**
14+
* STACK 풀이
15+
* TC: O(N), SC: O(N)
16+
* 스택 방식 작동 순서
17+
* for (const char of s) {
18+
if (여는 괄호) {
19+
스택에 push
20+
} else {
21+
스택에서 pop한 값이 char의 짝이 아니면 return false
22+
}
23+
}
24+
*/
25+
function isValid(s: string): boolean {
26+
const stack: string[] = [];
27+
const parentheseMap: Record<string, string> = {
28+
")": "(",
29+
"]": "[",
30+
"}": "{",
31+
};
32+
33+
for (const char of s) {
34+
if (["(", "[", "{"].includes(char)) {
35+
stack.push(char);
36+
} else {
37+
if (stack.pop() !== parentheseMap[char]) return false;
38+
}
39+
}
40+
return stack.length === 0;
41+
}
42+
43+
/**
44+
* Python과 JS에서의 pop() 메서드 차이
45+
* JS 같은 로직을 Python으로 바꾸면 Python에서만 다음과 같은 테스트 케이스 오류가 발생
46+
* TEST CASE: "]", "){", ")(){}"
47+
*
48+
* [원인]
49+
* if (stack.pop() !== parentheseMap[char]) return false; 비교시
50+
* JavaScript에서는 빈 스택 pop() -> undefined 반환으로 비교 가능!
51+
* Python에서는 빈 스택 pop() -> IndexError -> 오류 발생
52+
*
53+
* [해결책] - 예외 처리 필요
54+
* pop 전에 not stack 체크 꼭 해주기
55+
* not stack -> 스택이 비어 있다면 잘못된 닫는 괄호 먼저 나온 경우
56+
*/
57+
58+
// PY 풀이
59+
// class Solution:
60+
// def isValid(self, s: str) -> bool:
61+
// parentheseMap = {")" : "(", "]": "[", "}": "{"}
62+
// stack = []
63+
// open_parenthese = "([{"
64+
65+
// for char in s:
66+
// if char in open_parenthese:
67+
// stack.append(char)
68+
// else:
69+
// if (not stack or stack.pop() != parentheseMap[char]):
70+
// return False
71+
72+
// return len(stack) == 0

0 commit comments

Comments
 (0)