Skip to content

[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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
34 changes: 34 additions & 0 deletions longest-common-subsequence/hi-rachel.py
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]) # 다르면, 하나 줄인 상태 중 최댓값 선택
return dp[m][n]
46 changes: 46 additions & 0 deletions longest-repeating-character-replacement/hi-rachel.py
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
57 changes: 57 additions & 0 deletions palindromic-substrings/hi-rachel.py
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
69 changes: 69 additions & 0 deletions reverse-bits/hi-rachel.py
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)
"""

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)