-
-
Notifications
You must be signed in to change notification settings - Fork 194
[hi-rachel] Week 8 solutions #1503
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
a874a1d
c4d59b0
14051dc
6448249
0651078
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,60 @@ | ||
""" | ||
무방향 연결된 노드 | ||
그래프의 깊은 복사 반환해라 | ||
|
||
TC: O(V + E), V: 노드의 수, E: 이웃의 수 | ||
SC: O(V + E) | ||
|
||
# Definition for a Node. | ||
class Node: | ||
def __init__(self, val = 0, neighbors = None): | ||
self.val = val | ||
self.neighbors = neighbors if neighbors is not None else [] | ||
""" | ||
|
||
from typing import Optional | ||
|
||
class Node: | ||
def __init__(self, val = 0, neighbors = None): | ||
self.val = val | ||
self.neighbors = neighbors if neighbors is not None else [] | ||
|
||
# 깊이 우선 탐색 풀이 | ||
class Solution: | ||
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']: | ||
if not node: | ||
return | ||
|
||
clones = {} | ||
|
||
def dfs(node): | ||
if node in clones: | ||
return clones[node] | ||
clone = Node(node.val) | ||
clones[node] = clone | ||
for nei in node.neighbors: | ||
clone.neighbors.append(dfs(nei)) | ||
return clone | ||
|
||
return dfs(node) | ||
|
||
|
||
from collections import deque | ||
|
||
# 넓이 우선 탐색 풀이 | ||
class Solution: | ||
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']: | ||
if not node: | ||
return | ||
|
||
clone = Node(node.val) | ||
clones = {node: clone} | ||
queue = deque([node]) | ||
while queue: | ||
node = queue.popleft() | ||
for nei in node.neighbors: | ||
if nei not in clones: | ||
clones[nei] = Node(nei.val) | ||
queue.append(nei) | ||
clones[node].neighbors.append(clones[nei]) | ||
return clone |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
""" | ||
두 문자열 text1, text2가 주어졌을 때, 순서를 유지하는 공통 부분 수열 중 가장 긴 길이를 반환해라 | ||
- 없으면 0 반환 | ||
- 순서는 일치해야 하지만, 문자열 삭제 가능 | ||
- 소문자로만 이루어져 있음. | ||
- 1 <= text1.length, text2.length <= 1000 | ||
|
||
# LCS DP 풀이 | ||
- dp[i][j]: 문자열 text1[:i]와 text2[:j]까지의 LCS 길이 | ||
|
||
1. text1[i - 1] == text2[j - 1], 두 문자열이 같은 경우 | ||
LCS 길이 증가 | ||
dp[i][j] = dp[i - 1][j - 1] + 1 | ||
2. 다른 경우 | ||
text1[0...i] 또는 text2[0...j] 중 하나를 줄인 LCS 중 더 긴 쪽 선택 | ||
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) | ||
|
||
|
||
TC: O(m * n) | ||
SC: O(m * n) | ||
""" | ||
|
||
class Solution: | ||
def longestCommonSubsequence(self, text1: str, text2: str) -> int: | ||
m, n = len(text1), len(text2) | ||
dp = [[0] * (n + 1) for _ in range(m + 1)] # 0번째 인덱스를 비워둬서 문자열이 ""일 때를 기본값으로 처리 | ||
|
||
for i in range(1, m + 1): # text1의 1 ~ m 인덱스 | ||
for j in range(1, n + 1): # text2의 1 ~ n 인덱스 | ||
if text1[i - 1] == text2[j - 1]: | ||
dp[i][j] = dp[i - 1][j - 1] + 1 # 두 문자가 같으면, 이전 상태 + 1 | ||
else: | ||
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) # 다르면, 하나 줄인 상태 중 최댓값 선택 | ||
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. 저 또한 rachel 님처럼 처음에 2D DP(
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. @seungriyou 좋은 리뷰 감사합니다. 다시 풀때 최적화 해보도록 하겠습니다! |
||
return dp[m][n] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
""" | ||
주어진 문자열 s에서 최대 k번의 문자 교체를 통해 | ||
동일한 문자가 반복된 가장 긴 부분 문자열의 길이를 구해라 | ||
|
||
풀이: 슬라이딩 윈도우 + 빈도수 추적 | ||
"윈도우 [left, right] 구간에 대해, | ||
해당 구간에서 최대 등장하는 문자 하나를 기준으로 나머지 문자들을 최대 k번까지 바꿔서 동일 문자로 만들 수 있나?" | ||
-> 가능하면 윈도우 넓히기 | ||
-> 안되면 left를 줄여 윈도우 유지 | ||
|
||
현재 윈도우 길이: right - left + 1 | ||
윈도우 내 가장 자주 나온 문자 개수: max_cnt | ||
|
||
if (right - left + 1) - max_cnt <= k: | ||
# 이 윈도우는 k번 교체로 모두 동일 문자 가능 | ||
# -> 윈도우 확장 | ||
else: | ||
# 불가능 -> left를 오른쪽으로 줄여 윈도우 축소 (left + 1) | ||
|
||
TC: O(N) | ||
SC: O(1) -> dict는 최대 A-Z 26개의 키를 가짐 (상수 개수 제한) | ||
""" | ||
|
||
from collections import defaultdict | ||
|
||
class Solution: | ||
def characterReplacement(self, s: str, k: int) -> int: | ||
english_char_dict = defaultdict(int) | ||
max_len = 0 | ||
max_cnt = 0 # 현재 윈도우에서 가장 많이 나온 문자 수 | ||
left = 0 | ||
|
||
for right in range(len(s)): # 0 ~ len(s) | ||
english_char_dict[s[right]] += 1 # 문자 등장 횟수 계산 | ||
max_cnt = max(max_cnt, english_char_dict[s[right]]) # 최대 등장 횟수 갱신 | ||
|
||
# 현재 윈도우의 길이 = (right - left + 1) | ||
# 윈도우 내에서 바꿔야 할 문자 수가 k보다 크면 교체할 문자수가 너무 많으므로 윈도우 축소 | ||
if (right - left + 1) - max_cnt > k: | ||
english_char_dict[s[left]] -= 1 # 왼쪽 문자 제거 | ||
left += 1 # 윈도우 축소 | ||
|
||
# 윈도우가 유효한 경우, 그 윈도우 길이로 최대 길이 갱신 | ||
max_len = max(max_len, right - left + 1) | ||
|
||
return max_len |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
""" | ||
string s가 주어졌을 때, s에서 나올 수 있는 palindrome의 조건을 만족하는 substring 개수를 구하라. | ||
(s는 전부 소문자, 1이상 1000이하) | ||
|
||
1. 완전 탐색 | ||
- 나올 수 있는 모든 경우의 수 substring을 만들어 palindrome의 조건을 만족하는지 계산 | ||
|
||
TC: O(N^3) | ||
SC: O(N) | ||
|
||
2. 최적화 - 중심 확장 | ||
- 모든 palindrome은 어떤 중심을 기준으로 좌우 대칭인 원리를 이용 | ||
=> 문자열의 모든 위치를 중심으로 삼고, 양쪽으로 좌우를 확장하며 검사하면 됨 | ||
- 중심 개수: 2n - 1 | ||
|
||
TC: O(N^2) | ||
SC: O(1) | ||
|
||
3. 최적화 - DP | ||
1. 길이 1인 문자열은 항상 팰린드롬 | ||
dp[i][i] = True | ||
2. 길이 2인 문자열은 두 문자가 같으면 팰린드롬 | ||
s[i] == s[i+1] -> dp[i][i+1] = True | ||
3. 길이 3 이상인 문자열은 끝 두 문자열이 같고 안에 문자열도 모두 같아야 팰린드롬 | ||
s[i] == s[j] and dp[i+1][j-1] == True -> dp[i][j] = True | ||
(dp[i+1][j-1] == True시, s[i+1...j-1] 구간의 문자열이 이미 팰린드롬이라는 뜻) | ||
|
||
TC: O(N^2) | ||
SC: O(N^2) | ||
""" | ||
|
||
class Solution: | ||
def countSubstrings(self, s: str) -> int: | ||
cnt = 0 | ||
n = len(s) | ||
dp = [[False] * n for _ in range(n)] | ||
|
||
# 길이 1 => 항상 팰린드롬 | ||
for i in range(n): | ||
dp[i][i] = True | ||
cnt += 1 | ||
|
||
# 길이 2 => 같은 문자면 팰린드롬 | ||
for i in range(n-1): | ||
if s[i] == s[i+1]: # 서로 같은 문자면 | ||
dp[i][i+1] = True # 팰린드롬 | ||
cnt += 1 | ||
|
||
# 길이 3 이상 | ||
for length in range(3, n+1): # length는 부분 문자열의 길이 | ||
for i in range(n - length + 1): | ||
j = i + length - 1 # 끝 인덱스 | ||
if s[i] == s[j] and dp[i+1][j-1]: | ||
dp[i][j] = True | ||
cnt += 1 | ||
|
||
return cnt |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
""" | ||
1. 입력받은 정수 n을 32비트 이진수로 바꾼다 | ||
2. 이진수를 좌우로 뒤집는다 -> stack 활용 | ||
2. 뒤집은 이진수의 정수값을 반환한다 | ||
|
||
항상 32비트이므로 상수 시간, 상수 공간 | ||
TC: O(1) | ||
SC: O(1) | ||
""" | ||
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. 여러 방법으로 풀이해주셔서 많은 참고가 되었습니다! 😆 |
||
|
||
class Solution: | ||
def reverseBits(self, n: int) -> int: | ||
stack = [] | ||
while len(stack) < 32: | ||
stack.append(n % 2) | ||
n //= 2 | ||
|
||
output, scale = 0, 1 # 결과, 2^0 = 1 시작 | ||
while stack: | ||
output += stack.pop() * scale | ||
scale *= 2 | ||
|
||
return output | ||
|
||
""" | ||
비트 연산자 | ||
|
||
쉬프트 연산자 - 정수 타입에만 사용 가능, 내부적으로 이진수로 작동 | ||
<< 비트를 왼쪽으로 이동 | ||
x << 1 == x * 2 | ||
ex) 00001101 → 00011010 | ||
|
||
>> 비트를 오른쪽으로 이동 | ||
x >> 1 == x // 2 | ||
ex) 00001101 → 00000110 | ||
|
||
n & 1 | ||
현재 n의 가장 오른쪽 비트 확인 | ||
n & 1이 1이면 홀수, 0이면 짝수 | ||
""" | ||
|
||
class Solution: | ||
def reverseBits(self, n: int) -> int: | ||
stack = [] | ||
while len(stack) < 32: | ||
stack.append(n & 1) # 마지막 비트 1이면 1, 0이면 0 | ||
n >>= 1 # %= 2 와 같은 효과, 오른쪽 쉬프트 | ||
|
||
output, scale = 0, 1 # 결과, 2^0 = 1 시작 | ||
while stack: | ||
output += stack.pop() * scale | ||
scale <<= 1 # *= 2 와 같은 효과 | ||
|
||
return output | ||
|
||
# stack 공간 절약 풀이 | ||
class Solution: | ||
def reverseBits(self, n: int) -> int: | ||
output = 0 | ||
for _ in range(32): | ||
output <<= 1 # 왼쪽 쉬프트 | ||
output |= (n & 1) # 논리 연산자 사용 (제일 마지막 비트가 1이라면 1, 0이라면 0) | ||
n >>= 1 | ||
return output | ||
|
||
# int, format 활용 풀이 | ||
class Solution: | ||
def reverseBits(self, n: int) -> int: | ||
return int(format(n, "032b")[::-1], 2) |
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.
코드가 매우 직관적이고 알아보기 쉽게 작성된 것 같아요 🤩
다만
clones
에는 노드의 개수만큼 복제 노드가 저장되고, DFS의 경우 call stack이 최대 노드의 개수만큼, BFS의 경우queue
가 최대 노드의 개수만큼 커질 것이기 때문에 공간 복잡도는 O(V) 가 될 것 같습니다..!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.
@seungriyou 복제된 그래프의 전체 구조까지 포함한 공간을 고려하면
O(V + E)가 될 수 있을 것 같습니다!