Skip to content

[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

Merged
merged 6 commits into from
May 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions container-with-most-water/hi-rachel.py
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

파이썬은 특히나 TLE가 쉽게 나오는 것 같습니다. TS로 이런 로직을 구현하면 TLE가 나지는 않는지 궁금해집니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@river20s 저는 파이썬에서 타입스크립트로 바꾼다고 시간, 공간 복잡도가 크게 바뀐 경우는 본 적이 없는 것 같습니다. 어떤 로직과 자료구조를 사용하는지가 중요하고 둘 다 인터프리터 언어이기 때문에 큰 차이는 없다고 생각합니다.

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

23 changes: 23 additions & 0 deletions container-with-most-water/hi-rachel.ts
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;
}
38 changes: 38 additions & 0 deletions design-add-and-search-words-data-structure/hi-rachel.py
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에 없으면
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

트라이를 사용하신 것 같은데 메서드 안에 직접 구현하신 부분이 읽을 때 깔끔하게 느껴졌습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

26은 변하지 않는 상수이기 때문에 여기서 TC를 O(W)로 봐도 무방하지 않을까 생각합니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
48 changes: 48 additions & 0 deletions design-add-and-search-words-data-structure/hi-rachel.ts
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)
*/
53 changes: 53 additions & 0 deletions longest-increasing-subsequence/hi-rachel.py
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)
21 changes: 21 additions & 0 deletions longest-increasing-subsequence/hi-rachel.ts
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); // 숫자 배열을 펼쳐 여러 숫자를 넘겨줘야 함
}
88 changes: 88 additions & 0 deletions spiral-matrix/hi-rachel.py
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

72 changes: 72 additions & 0 deletions valid-parentheses/hi-rachel.ts
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