Skip to content

Commit ce12735

Browse files
authored
Merge pull request #1503 from hi-rachel/main
[hi-rachel] Week 8 solutions
2 parents 39ef7b2 + 0651078 commit ce12735

File tree

5 files changed

+266
-0
lines changed

5 files changed

+266
-0
lines changed

clone-graph/hi-rachel.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""
2+
무방향 연결된 노드
3+
그래프의 깊은 복사 반환해라
4+
5+
TC: O(V + E), V: 노드의 수, E: 이웃의 수
6+
SC: O(V + E)
7+
8+
# Definition for a Node.
9+
class Node:
10+
def __init__(self, val = 0, neighbors = None):
11+
self.val = val
12+
self.neighbors = neighbors if neighbors is not None else []
13+
"""
14+
15+
from typing import Optional
16+
17+
class Node:
18+
def __init__(self, val = 0, neighbors = None):
19+
self.val = val
20+
self.neighbors = neighbors if neighbors is not None else []
21+
22+
# 깊이 우선 탐색 풀이
23+
class Solution:
24+
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
25+
if not node:
26+
return
27+
28+
clones = {}
29+
30+
def dfs(node):
31+
if node in clones:
32+
return clones[node]
33+
clone = Node(node.val)
34+
clones[node] = clone
35+
for nei in node.neighbors:
36+
clone.neighbors.append(dfs(nei))
37+
return clone
38+
39+
return dfs(node)
40+
41+
42+
from collections import deque
43+
44+
# 넓이 우선 탐색 풀이
45+
class Solution:
46+
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:
47+
if not node:
48+
return
49+
50+
clone = Node(node.val)
51+
clones = {node: clone}
52+
queue = deque([node])
53+
while queue:
54+
node = queue.popleft()
55+
for nei in node.neighbors:
56+
if nei not in clones:
57+
clones[nei] = Node(nei.val)
58+
queue.append(nei)
59+
clones[node].neighbors.append(clones[nei])
60+
return clone
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""
2+
두 문자열 text1, text2가 주어졌을 때, 순서를 유지하는 공통 부분 수열 중 가장 긴 길이를 반환해라
3+
- 없으면 0 반환
4+
- 순서는 일치해야 하지만, 문자열 삭제 가능
5+
- 소문자로만 이루어져 있음.
6+
- 1 <= text1.length, text2.length <= 1000
7+
8+
# LCS DP 풀이
9+
- dp[i][j]: 문자열 text1[:i]와 text2[:j]까지의 LCS 길이
10+
11+
1. text1[i - 1] == text2[j - 1], 두 문자열이 같은 경우
12+
LCS 길이 증가
13+
dp[i][j] = dp[i - 1][j - 1] + 1
14+
2. 다른 경우
15+
text1[0...i] 또는 text2[0...j] 중 하나를 줄인 LCS 중 더 긴 쪽 선택
16+
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
17+
18+
19+
TC: O(m * n)
20+
SC: O(m * n)
21+
"""
22+
23+
class Solution:
24+
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
25+
m, n = len(text1), len(text2)
26+
dp = [[0] * (n + 1) for _ in range(m + 1)] # 0번째 인덱스를 비워둬서 문자열이 ""일 때를 기본값으로 처리
27+
28+
for i in range(1, m + 1): # text1의 1 ~ m 인덱스
29+
for j in range(1, n + 1): # text2의 1 ~ n 인덱스
30+
if text1[i - 1] == text2[j - 1]:
31+
dp[i][j] = dp[i - 1][j - 1] + 1 # 두 문자가 같으면, 이전 상태 + 1
32+
else:
33+
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # 다르면, 하나 줄인 상태 중 최댓값 선택
34+
return dp[m][n]
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
주어진 문자열 s에서 최대 k번의 문자 교체를 통해
3+
동일한 문자가 반복된 가장 긴 부분 문자열의 길이를 구해라
4+
5+
풀이: 슬라이딩 윈도우 + 빈도수 추적
6+
"윈도우 [left, right] 구간에 대해,
7+
해당 구간에서 최대 등장하는 문자 하나를 기준으로 나머지 문자들을 최대 k번까지 바꿔서 동일 문자로 만들 수 있나?"
8+
-> 가능하면 윈도우 넓히기
9+
-> 안되면 left를 줄여 윈도우 유지
10+
11+
현재 윈도우 길이: right - left + 1
12+
윈도우 내 가장 자주 나온 문자 개수: max_cnt
13+
14+
if (right - left + 1) - max_cnt <= k:
15+
# 이 윈도우는 k번 교체로 모두 동일 문자 가능
16+
# -> 윈도우 확장
17+
else:
18+
# 불가능 -> left를 오른쪽으로 줄여 윈도우 축소 (left + 1)
19+
20+
TC: O(N)
21+
SC: O(1) -> dict는 최대 A-Z 26개의 키를 가짐 (상수 개수 제한)
22+
"""
23+
24+
from collections import defaultdict
25+
26+
class Solution:
27+
def characterReplacement(self, s: str, k: int) -> int:
28+
english_char_dict = defaultdict(int)
29+
max_len = 0
30+
max_cnt = 0 # 현재 윈도우에서 가장 많이 나온 문자 수
31+
left = 0
32+
33+
for right in range(len(s)): # 0 ~ len(s)
34+
english_char_dict[s[right]] += 1 # 문자 등장 횟수 계산
35+
max_cnt = max(max_cnt, english_char_dict[s[right]]) # 최대 등장 횟수 갱신
36+
37+
# 현재 윈도우의 길이 = (right - left + 1)
38+
# 윈도우 내에서 바꿔야 할 문자 수가 k보다 크면 교체할 문자수가 너무 많으므로 윈도우 축소
39+
if (right - left + 1) - max_cnt > k:
40+
english_char_dict[s[left]] -= 1 # 왼쪽 문자 제거
41+
left += 1 # 윈도우 축소
42+
43+
# 윈도우가 유효한 경우, 그 윈도우 길이로 최대 길이 갱신
44+
max_len = max(max_len, right - left + 1)
45+
46+
return max_len

palindromic-substrings/hi-rachel.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
string s가 주어졌을 때, s에서 나올 수 있는 palindrome의 조건을 만족하는 substring 개수를 구하라.
3+
(s는 전부 소문자, 1이상 1000이하)
4+
5+
1. 완전 탐색
6+
- 나올 수 있는 모든 경우의 수 substring을 만들어 palindrome의 조건을 만족하는지 계산
7+
8+
TC: O(N^3)
9+
SC: O(N)
10+
11+
2. 최적화 - 중심 확장
12+
- 모든 palindrome은 어떤 중심을 기준으로 좌우 대칭인 원리를 이용
13+
=> 문자열의 모든 위치를 중심으로 삼고, 양쪽으로 좌우를 확장하며 검사하면 됨
14+
- 중심 개수: 2n - 1
15+
16+
TC: O(N^2)
17+
SC: O(1)
18+
19+
3. 최적화 - DP
20+
1. 길이 1인 문자열은 항상 팰린드롬
21+
dp[i][i] = True
22+
2. 길이 2인 문자열은 두 문자가 같으면 팰린드롬
23+
s[i] == s[i+1] -> dp[i][i+1] = True
24+
3. 길이 3 이상인 문자열은 끝 두 문자열이 같고 안에 문자열도 모두 같아야 팰린드롬
25+
s[i] == s[j] and dp[i+1][j-1] == True -> dp[i][j] = True
26+
(dp[i+1][j-1] == True시, s[i+1...j-1] 구간의 문자열이 이미 팰린드롬이라는 뜻)
27+
28+
TC: O(N^2)
29+
SC: O(N^2)
30+
"""
31+
32+
class Solution:
33+
def countSubstrings(self, s: str) -> int:
34+
cnt = 0
35+
n = len(s)
36+
dp = [[False] * n for _ in range(n)]
37+
38+
# 길이 1 => 항상 팰린드롬
39+
for i in range(n):
40+
dp[i][i] = True
41+
cnt += 1
42+
43+
# 길이 2 => 같은 문자면 팰린드롬
44+
for i in range(n-1):
45+
if s[i] == s[i+1]: # 서로 같은 문자면
46+
dp[i][i+1] = True # 팰린드롬
47+
cnt += 1
48+
49+
# 길이 3 이상
50+
for length in range(3, n+1): # length는 부분 문자열의 길이
51+
for i in range(n - length + 1):
52+
j = i + length - 1 # 끝 인덱스
53+
if s[i] == s[j] and dp[i+1][j-1]:
54+
dp[i][j] = True
55+
cnt += 1
56+
57+
return cnt

reverse-bits/hi-rachel.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
1. 입력받은 정수 n을 32비트 이진수로 바꾼다
3+
2. 이진수를 좌우로 뒤집는다 -> stack 활용
4+
2. 뒤집은 이진수의 정수값을 반환한다
5+
6+
항상 32비트이므로 상수 시간, 상수 공간
7+
TC: O(1)
8+
SC: O(1)
9+
"""
10+
11+
class Solution:
12+
def reverseBits(self, n: int) -> int:
13+
stack = []
14+
while len(stack) < 32:
15+
stack.append(n % 2)
16+
n //= 2
17+
18+
output, scale = 0, 1 # 결과, 2^0 = 1 시작
19+
while stack:
20+
output += stack.pop() * scale
21+
scale *= 2
22+
23+
return output
24+
25+
"""
26+
비트 연산자
27+
28+
쉬프트 연산자 - 정수 타입에만 사용 가능, 내부적으로 이진수로 작동
29+
<< 비트를 왼쪽으로 이동
30+
x << 1 == x * 2
31+
ex) 00001101 → 00011010
32+
33+
>> 비트를 오른쪽으로 이동
34+
x >> 1 == x // 2
35+
ex) 00001101 → 00000110
36+
37+
n & 1
38+
현재 n의 가장 오른쪽 비트 확인
39+
n & 1이 1이면 홀수, 0이면 짝수
40+
"""
41+
42+
class Solution:
43+
def reverseBits(self, n: int) -> int:
44+
stack = []
45+
while len(stack) < 32:
46+
stack.append(n & 1) # 마지막 비트 1이면 1, 0이면 0
47+
n >>= 1 # %= 2 와 같은 효과, 오른쪽 쉬프트
48+
49+
output, scale = 0, 1 # 결과, 2^0 = 1 시작
50+
while stack:
51+
output += stack.pop() * scale
52+
scale <<= 1 # *= 2 와 같은 효과
53+
54+
return output
55+
56+
# stack 공간 절약 풀이
57+
class Solution:
58+
def reverseBits(self, n: int) -> int:
59+
output = 0
60+
for _ in range(32):
61+
output <<= 1 # 왼쪽 쉬프트
62+
output |= (n & 1) # 논리 연산자 사용 (제일 마지막 비트가 1이라면 1, 0이라면 0)
63+
n >>= 1
64+
return output
65+
66+
# int, format 활용 풀이
67+
class Solution:
68+
def reverseBits(self, n: int) -> int:
69+
return int(format(n, "032b")[::-1], 2)

0 commit comments

Comments
 (0)