diff --git a/clone-graph/KwonNayeon.py b/clone-graph/KwonNayeon.py index bec1e5a1e..96c7d6279 100644 --- a/clone-graph/KwonNayeon.py +++ b/clone-graph/KwonNayeon.py @@ -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 @@ -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) - diff --git a/longest-common-subsequence/KwonNayeon.py b/longest-common-subsequence/KwonNayeon.py index 47c9b2263..8d7616ee6 100644 --- a/longest-common-subsequence/KwonNayeon.py +++ b/longest-common-subsequence/KwonNayeon.py @@ -3,6 +3,8 @@ - 1 <= text1.length, text2.length <= 1000 - text1 and text2 consist of only lowercase English characters. + + Time Complexity: O(m*n) - m은 text1의 길이, n은 text2의 길이 - @cache로 중복 계산을 방지하여 각 (i,j) 조합을 한 번만 계산함 @@ -17,7 +19,6 @@ - 다르면: 한쪽만 이동한 경우 중 최댓값 선택 3. base case: 어느 한쪽 문자열 끝에 도달하면 종료 """ - from functools import cache class Solution: def longestCommonSubsequence(self, text1: str, text2: str) -> int: @@ -32,3 +33,48 @@ def dfs(i, j): return max(dfs(i + 1, j), dfs(i, j + 1)) return dfs(0, 0) + +""" + + +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] diff --git a/longest-repeating-character-replacement/KwonNayeon.py b/longest-repeating-character-replacement/KwonNayeon.py index f219491d0..b25b041bb 100644 --- a/longest-repeating-character-replacement/KwonNayeon.py +++ b/longest-repeating-character-replacement/KwonNayeon.py @@ -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 diff --git a/palindromic-substrings/KwonNayeon.py b/palindromic-substrings/KwonNayeon.py new file mode 100644 index 000000000..daae68fda --- /dev/null +++ b/palindromic-substrings/KwonNayeon.py @@ -0,0 +1,71 @@ +""" +Constraints: +- 1 <= s.length <= 1000 +- s consists of lowercase English letters. + + + +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 + diff --git a/reverse-bits/KwonNayeon.py b/reverse-bits/KwonNayeon.py index 871b50943..a65f87aed 100644 --- a/reverse-bits/KwonNayeon.py +++ b/reverse-bits/KwonNayeon.py @@ -15,7 +15,6 @@ 2. 문자열 슬라이싱 [::-1]으로 비트를 뒤집음 3. int(reversed_binary, 2)로 뒤집은 이진수 문자열을 다시 정수로 변환함 """ - class Solution: def reverseBits(self, n: int) -> int: @@ -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) """ -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