Skip to content

[KwonNayeon] Week 8 solutions #1501

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 5 commits into from
May 24, 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
22 changes: 14 additions & 8 deletions clone-graph/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,25 @@
- N: 노드의 개수
- dictionary와 재귀 호출 스택 공간

풀이 방법:
- 문제에서 요구하는 것: Deep copy
- Base case: 빈 그래프 처리
- 딕셔너리 생성 후 dfs
- 만약 이미 복사한 노드라면 해당 복사본을 반환함
- 아니라면, 새로운 노드를 생성하여 딕셔너리에 저장
- 이웃 노드의 경우에도 dfs()로 복사본을 만들어서 현재 노드의 neighbors 리스트에 추가함
- 주어진 노드부터 재귀 시작

# 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 []

참고 사항:
- 혼자 풀기 어려워서, 문제와 답을 이해하는 것에 집중했습니다!
"""
class Solution:
def cloneGraph(self, node: Optional['Node']) -> Optional['Node']:

# 빈 그래프 처리
if not node:
return None

Expand All @@ -51,13 +59,11 @@ def dfs(node):

# 새로운 노드 생성
copy = Node(node.val)
dict[node.val] = copy # dictionary에 기록
dict[node.val] = copy

# 각 neighbor에 대해서도 같은 과정 수행
for neighbor in node.neighbors:
copy.neighbors.append(dfs(neighbor))
for neighbor in node.neighbors: # 원본의 각 이웃에 대하여
copy.neighbors.append(dfs(neighbor)) # 그 이웃의 복사본을 만들어서 추가함

return copy

return dfs(node)

48 changes: 47 additions & 1 deletion longest-common-subsequence/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- 1 <= text1.length, text2.length <= 1000
- text1 and text2 consist of only lowercase English characters.

<Solution 1: DFS, 메모이제이션 활용>

Time Complexity: O(m*n)
- m은 text1의 길이, n은 text2의 길이
- @cache로 중복 계산을 방지하여 각 (i,j) 조합을 한 번만 계산함
Expand All @@ -17,7 +19,6 @@
- 다르면: 한쪽만 이동한 경우 중 최댓값 선택
3. base case: 어느 한쪽 문자열 끝에 도달하면 종료
"""

from functools import cache
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
Expand All @@ -32,3 +33,48 @@ def dfs(i, j):
return max(dfs(i + 1, j), dfs(i, j + 1))

return dfs(0, 0)

"""
<Solution 2: DP>

Time Complexity: O(n * m)
- 2중 for문으로 모든 dp[i][j] 계산

Space Complexity: O(n * m)
- (n+1) * (m+1) 크기의 DP 테이블 사용

풀이방법:
- 상태 정의: dp[i][j] = text1[:i]와 text2[:j]의 LCS 길이
- Subsequence: 순서만 유지하면 됨
- Substring: 연속적으로 나타나야 함

점화식:
- 문자가 같으면: dp[i][j] = dp[i-1][j-1] + 1
- 문자가 다르면: dp[i][j] = max(dp[i-1][j], dp[i][j-1])

핵심:
- dp 테이블 크기 (n+1) * (m+1): 빈 문자열 케이스 포함
- 최종 답: dp[n][m] (전체 문자열 비교 결과)

노트:
- DP 패턴을 찾아내는 연습하기
- 매트릭스 활용
"""
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
n, m = len(text1), len(text2)

# DP 테이블 초기화
dp = [[0] * (m + 1) for _ in range(n + 1)]

# DP 테이블 채우기
for i in range(1, n + 1):
for j in range(1, m + 1):
if text1[i-1] == text2[j-1]:
# 문자가 같으면: 둘 다 선택 + 이전 결과
dp[i][j] = 1 + dp[i-1][j-1]
else:
# 문자가 다르면: 둘 중 하나 제외하고 최대값
dp[i][j] = max(dp[i-1][j], dp[i][j-1])

return dp[n][m]
45 changes: 27 additions & 18 deletions longest-repeating-character-replacement/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,41 @@
- 여기서 n은 문자열의 길이

Space Complexity: O(1)
- 추가 변수(left, right, max_length 등)는 상수 개
- 추가 변수(max_length, max_count, start, end 등) 이외의 공간 사용하지 않음

풀이방법:
1. Sliding Window로 구간을 관리
- right 포인터로 구간을 늘리다가
- 변경해야하는 문자 수가 k를 초과하면 left 포인터로 구간을 줄임
- end 포인터로 구간을 늘리다가
- 변경해야하는 문자 수가 k를 초과하면 start 포인터로 구간을 줄임

2. 각 구간에서:
- 가장 많이 등장한 문자로 나머지를 변경
- 나머지 문자를 가장 많이 등장한 문자로 변경
- (구간 길이 - 가장 많이 등장한 문자 수)가 k 이하여야 함
"""
class Solution:
def characterReplacement(self, s: str, k: int) -> int:
counter = {}
left = 0
max_length = 0

for right in range(len(s)):
counter[s[right]] = counter.get(s[right], 0) + 1

curr_length = right - left + 1
from collections import defaultdict

if curr_length - max(counter.values()) > k:
counter[s[left]] -= 1
left += 1

max_length = max(max_length, right - left + 1)

max_length = 0
max_count = 0
start = 0
char_count = defaultdict(int)

for end in range(len(s)):
# 현재 문자의 등장 횟수 증가
char_count[s[end]] += 1

# 윈도우 내 가장 많이 나타난 문자의 등장 횟수 업데이트
max_count = max(max_count, char_count[s[end]])

# 윈도우의 크기 - 가장 많이 나타난 문자의 등장 횟수 = 변경해야 할 문자의 수
# 이 때 변경하는 문자의 종류는 상관없음
# 이 값이 k보다 클 때, 윈도우의 크기를 줄임
if (end - start + 1) - max_count > k:
char_count[s[start]] -= 1
start += 1

# 최대 길이 업데이트
max_length = max(max_length, end - start + 1)

return max_length
71 changes: 71 additions & 0 deletions palindromic-substrings/KwonNayeon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""
Constraints:
- 1 <= s.length <= 1000
- s consists of lowercase English letters.

<Solution 2>

Time Complexity: O(n²)
- 외부 for문: n번 실행
- 내부 함수 (expand_around_center): 최악의 경우 n번 확장
- 결과: n * n = O(n²)

Space Complexity: O(1)
- 상수 공간만 사용 (count, left, right)

풀이 방법:
1. expand_around_center 헬퍼 함수:
- 주어진 중심에서 양쪽으로 확장하며 팰린드롬의 개수를 셈
- 경계 체크, 문자 일치여부 확인

2. 각 위치에서 두 가지 경우 확인:
- 홀수 길이: expand_around_center(i, i) - 중심이 한 글자
- 짝수 길이: expand_around_center(i, i+1) - 중심이 두 글자

핵심 아이디어:
- 중심에서 바깥으로 확장 (안에서 밖으로)
- 모든 가능한 중심점에서 팰린드롬 탐색

노트:
- dp로도 풀어보기
"""
# Solution 1: Brute-force
class Solution:
def countSubstrings(self, s: str) -> int:
def isPalindromic(start, end):
while start < end:
if s[start] != s[end]:
return False
start, end = start + 1, end - 1
return True

cnt = 0

for i in range(0, len(s)):
for j in range(i, len(s)):
if isPalindromic(i, j):
cnt += 1
return cnt

# Solution 2
class Solution:
def countSubstrings(self, s: str) -> int:
def expand_around_center(left, right):
cnt = 0

while left >= 0 and right < len(s) and s[left] == s[right]:
cnt += 1
left -= 1
right += 1
return cnt

total = 0

for i in range(len(s)):
# 홀수일 때
total += expand_around_center(i, i)
# 짝수일 때
total += expand_around_center(i, i+1)

return total

22 changes: 19 additions & 3 deletions reverse-bits/KwonNayeon.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
2. 문자열 슬라이싱 [::-1]으로 비트를 뒤집음
3. int(reversed_binary, 2)로 뒤집은 이진수 문자열을 다시 정수로 변환함
"""

class Solution:
def reverseBits(self, n: int) -> int:

Expand All @@ -24,12 +23,29 @@ def reverseBits(self, n: int) -> int:
reversed_binary = binary[::-1]

return int(reversed_binary, 2)

# 코드를 간결하게 정리한 버전
class Solution:
def reverseBits(self, n: int) -> int:

return int(format(n, '032b')[::-1], 2)
"""
<Solution 2>

Time Complexity:
Time Complexity: O(1)
- 각 반복에서 비트 연산은 상수 시간이 걸림

Space Complexity:
Space Complexity: O(1)
- 사용되는 변수는 result와 입력값 n밖에 없음

풀이 방법:
- ...
"""
class Solution:
def reverseBits(self, n: int) -> int:
result = 0
for i in range(32):
result <<= 1 # 결과를 왼쪽으로 한 칸 밀고
result |= n & 1 # n의 마지막 비트를 결과에 추가
n >>= 1 # n을 오른쪽으로 한 칸 밀어 다음 비트로 이동
return result