From cfdf2e599ed2b20dd0a872941aa86ca7a738d6a1 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Wed, 21 May 2025 00:04:50 +0900 Subject: [PATCH 1/6] solve(w08): 190. Reverse Bits --- reverse-bits/seungriyou.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 reverse-bits/seungriyou.py diff --git a/reverse-bits/seungriyou.py b/reverse-bits/seungriyou.py new file mode 100644 index 000000000..115d1a9e2 --- /dev/null +++ b/reverse-bits/seungriyou.py @@ -0,0 +1,32 @@ +# https://leetcode.com/problems/reverse-bits/ + +class Solution: + def reverseBits_32(self, n: int) -> int: + """ + [Complexity] + - TC: O(32) + - SC: O(1) + + [Approach] + n의 맨 오른쪽 bit부터 res의 맨 왼쪽에 붙여나가기 + """ + res = 0 + for i in range(32): + res |= ((n >> i) & 1) << (31 - i) + return res + + def reverseBits(self, n: int) -> int: + """ + [Complexity] + - TC: O(16) + - SC: O(1) + + [Approach] + n의 바깥쪽에서부터 two pointer 처럼 res에 모으기 + """ + res = 0 + for i in range(16): + left = (n >> (31 - i)) & 1 + right = (n >> i) & 1 + res |= (left << i) | (right << (31 - i)) + return res From aa1d93e14d5e11306d148a6f2f3443c7243f96c8 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Thu, 22 May 2025 23:43:10 +0900 Subject: [PATCH 2/6] solve(w08): 133. Clone Graph --- clone-graph/seungriyou.py | 82 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 clone-graph/seungriyou.py diff --git a/clone-graph/seungriyou.py b/clone-graph/seungriyou.py new file mode 100644 index 000000000..7d4332c60 --- /dev/null +++ b/clone-graph/seungriyou.py @@ -0,0 +1,82 @@ +# https://leetcode.com/problems/clone-graph/ + +# 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 Solution: + def cloneGraph_dfs(self, node: Optional['Node']) -> Optional['Node']: + """ + [Complexity] + - TC: O(n + m) (n = node 개수, m = edge 개수) + - SC: O(n) (call stack & cache) + + [Approach] + {val: node} 형태의 dict를 캐시처럼 활용하여, DFS로 접근한다. + 각 node를 복제해야 하므로, 원본 node의 복제본을 O(1)에 lookup 하려면 dict에 key를 Node.val 값으로 설정해야 한다. + """ + copies = dict() # {val: node} + + if not node: + return None + + def copy_node(curr): + # base condition: 이미 copies에 존재하는 node라면 반환 + if curr.val in copies: + return copies[curr.val] + + # 현재 노드 복사 + copied_node = Node(val=curr.val) + + # copies에 추가 + copies[curr.val] = copied_node + + # neighbors를 순회하며, neighbors에 copy를 만들어 추가 + for ngbr in curr.neighbors: + copied_node.neighbors.append(copy_node(ngbr)) + + # 복사한 노드 반환 + return copied_node + + return copy_node(node) + + def cloneGraph(self, node: Optional['Node']) -> Optional['Node']: + """ + [Complexity] + - TC: O(n + m) + - SC: O(n) (queue & cache) + + [Approach] + {val: node} 형태의 dict를 캐시처럼 활용하여, BFS로 접근한다. + """ + from collections import deque + + copies = dict() # {val: node} + + if not node: + return None + + q = deque([node]) + copied_root_node = Node(val=node.val) + copies[node.val] = copied_root_node + + while q: + curr = q.popleft() + + for ngbr in curr.neighbors: + # ngbr.val이 캐시에 존재하지 않으면, copy 후 캐시에 저장 + if ngbr.val not in copies: + # ngbr의 복사본 생성 및 캐시에 저장 + copies[ngbr.val] = Node(val=ngbr.val) + + # ngbr은 아직 방문되지 않았으므로, q에 추가 + q.append(ngbr) + + # curr 복사본의 neighbors에 ngbr 복사본 추가 + copies[curr.val].neighbors.append(copies[ngbr.val]) + + return copied_root_node From 887b8d9be6387c54ef73938c52b7a8c7e91f0472 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Thu, 22 May 2025 23:43:37 +0900 Subject: [PATCH 3/6] solve(w08): 424. Longest Repeating Character Replacement --- .../seungriyou.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 longest-repeating-character-replacement/seungriyou.py diff --git a/longest-repeating-character-replacement/seungriyou.py b/longest-repeating-character-replacement/seungriyou.py new file mode 100644 index 000000000..6f45f969b --- /dev/null +++ b/longest-repeating-character-replacement/seungriyou.py @@ -0,0 +1,39 @@ +# https://leetcode.com/problems/longest-repeating-character-replacement/ + +class Solution: + def characterReplacement(self, s: str, k: int) -> int: + """ + [Complexity] + - TC: O(n) + - SC: O(n) + + [Approach] + two pointer로 sliding window를 이동해가면서, k번 이내로 replace 했을 때 모두 같은 문자가 될 때의 max_len를 트래킹하면 된다. + 이때, 이 substring을 모두 같은 문자로 만들 수 있는 가장 작은 replacement 횟수를 구해 k와 비교해야 하는데, + 이 횟수 max_replace는 (substring의 길이 - 가장 빈도가 높은 문자의 등장 횟수) 이다. + max_replace가 k 이하라면 max_len을 업데이트하고, 아니라면 left를 한 칸 전진한다. + """ + from collections import defaultdict + + left = max_len = max_freq = 0 # left ~ right: k번 이내로 replace 했을 때, 모두 같은 문자일 때의 max_len 업데이트 + cnt = defaultdict(int) # left ~ right sliding window 내에서의 counter + + # right 한 칸씩 이동해가며 확인 + for right in range(len(s)): + # max_freq 업데이트 + cnt[s[right]] += 1 + max_freq = max(max_freq, cnt[s[right]]) + + # 현재 sliding window를 모두 같은 문자로 만들 수 있는 가장 작은 replacement 횟수 구하기 + sub_len = right - left + 1 + min_replace = sub_len - max_freq + + # min_replace가 k 이하이면, max_len 업데이트 + if min_replace <= k: + max_len = max(max_len, sub_len) + # 아니라면, left 한 칸 이동 + else: + cnt[s[left]] -= 1 + left += 1 + + return max_len From ca66ab0de5dae62e87c58437f72641b2db8396a7 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Thu, 22 May 2025 23:48:12 +0900 Subject: [PATCH 4/6] solve(w08): 424. Longest Repeating Character Replacement (modified) --- longest-repeating-character-replacement/seungriyou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/longest-repeating-character-replacement/seungriyou.py b/longest-repeating-character-replacement/seungriyou.py index 6f45f969b..599a147f6 100644 --- a/longest-repeating-character-replacement/seungriyou.py +++ b/longest-repeating-character-replacement/seungriyou.py @@ -5,7 +5,7 @@ def characterReplacement(self, s: str, k: int) -> int: """ [Complexity] - TC: O(n) - - SC: O(n) + - SC: O(1) (* s consists of only uppercase English letters) [Approach] two pointer로 sliding window를 이동해가면서, k번 이내로 replace 했을 때 모두 같은 문자가 될 때의 max_len를 트래킹하면 된다. From cb252a13d375a0ed0495f6fdb7676f5061f42da9 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Sat, 24 May 2025 00:04:34 +0900 Subject: [PATCH 5/6] solve(w08): 1143. Longest Common Subsequence --- longest-common-subsequence/seungriyou.py | 53 ++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 longest-common-subsequence/seungriyou.py diff --git a/longest-common-subsequence/seungriyou.py b/longest-common-subsequence/seungriyou.py new file mode 100644 index 000000000..0b6d97cc9 --- /dev/null +++ b/longest-common-subsequence/seungriyou.py @@ -0,0 +1,53 @@ +# https://leetcode.com/problems/longest-common-subsequence/ + +class Solution: + def longestCommonSubsequence_2d(self, text1: str, text2: str) -> int: + """ + [Complexity] + - TC: O(m * n) + - SC: O(m * n) + + [Approach] + dp[i][j] = text1[:i]와 text2[:j]의 longest common subsequence의 길이 + = (text1[i - 1] == text2[j - 1] 라면) dp[i - 1][j - 1] + 1 (text1[:i - 1]와 text2[:j - 1]의 longest common subsequence 길이) + (text1[i - 1] != text2[j - 1] 라면) max(dp[i - 1][j], dp[i][j - 1]) + """ + m, n = len(text1), len(text2) + + dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if text1[i - 1] == text2[j - 1]: + dp[i][j] = dp[i - 1][j - 1] + 1 + else: + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + + return dp[-1][-1] + + def longestCommonSubsequence(self, text1: str, text2: str) -> int: + """ + [Complexity] + - TC: O(m * n) + - SC: O(n) + + [Approach] + 2D DP에서, 현재 row와 이전 row만 필요로 하기 때문에 1D DP로 space optimize 할 수 있다. + dp[i]를 curr로, dp[i - 1]를 prev로 유지한다. (이때, curr와 prev는 len이 n + 1이다.) + """ + m, n = len(text1), len(text2) + + prev = [0 for _ in range(n + 1)] + curr = [0 for _ in range(n + 1)] + + for i in range(1, m + 1): + for j in range(1, n + 1): + if text1[i - 1] == text2[j - 1]: + curr[j] = prev[j - 1] + 1 + else: + curr[j] = max(prev[j], curr[j - 1]) + + # 다음 row로 넘어가기 위해 바꿔치기 + curr, prev = prev, curr + + return prev[-1] From d033c2a498ec5f7e5f546ce5c76b74611ca04d06 Mon Sep 17 00:00:00 2001 From: seungriyou Date: Sat, 24 May 2025 00:58:38 +0900 Subject: [PATCH 6/6] solve(w08): 647. Palindromic Substrings --- palindromic-substrings/seungriyou.py | 116 +++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 palindromic-substrings/seungriyou.py diff --git a/palindromic-substrings/seungriyou.py b/palindromic-substrings/seungriyou.py new file mode 100644 index 000000000..32d765fb7 --- /dev/null +++ b/palindromic-substrings/seungriyou.py @@ -0,0 +1,116 @@ +# https://leetcode.com/problems/palindromic-substrings/ + +class Solution: + def countSubstrings_dp(self, s: str) -> int: + """ + [Complexity] + - TC: O(n^2) + - SC: O(n^2) + + [Approach] + palindromic substring인지 여부를 확인하기 위해, 크게 두 가지 경우에 대해 고려해야 한다. + - 길이가 홀수: len == 1인 단위 substring에서 좌우로 확장 + - 길이가 짝수: len == 2인 단위 substring에서 좌우로 확장 + + 이때, 기존의 짧은 substring에서 판단한 결과를 계속해서 재사용하므로 다음의 DP table을 사용하는 2D DP로 풀 수 있다. + dp[i][j] = s[i:j + 1]이 palindromic substring인지 여부 + + 따라서 다음과 같이 1) & 2)에서 길이가 각각 1, 2인 단위 substring에 대해 초기화를 먼저 수행하고, 3)에서 len >= 3이상인 substring에 대해 판단한다. + 단, **더 짧은 안 쪽 substring에서의 판단 결과를 사용해야 하므로** len을 3부터 n까지 늘려가면서, two pointer i & j로 판단한다. + 1) length = 1 : dp[i][i]은 무조건 True + 2) length = 2 : dp[i][i + 1]은 s[i] == s[i + 1]이면 True + 3) length >= 3 : (j = i + length - 1일 때) dp[i][j]은 (s[i] == s[j]) && (dp[i + 1][j - 1])이면 True + """ + + n = len(s) + dp = [[False for _ in range(n)] for _ in range(n)] + res = 0 + + # 1) length = 1 : dp[i][i]은 무조건 True + for i in range(n): + dp[i][i] = True + res += 1 + + # 2) length = 2 : dp[i][i + 1]은 s[i] == s[i + 1]이면 True + for i in range(n - 1): + if s[i] == s[i + 1]: + dp[i][i + 1] = True + res += 1 + + # 3) length >= 3 : (j = i + length - 1일 때) dp[i][j]은 s[i] == s[j]이면서 dp[i][j - 1]이면 True + for length in range(3, n + 1): # length는 3부터 n까지 늘려나가기 (**더 짧은 substring에서의 판단 결과를 사용해야 하므로**) + for i in range(n - length + 1): # i = substring 시작 인덱스 + j = i + length - 1 # j = substring 종료 인덱스 + if s[i] == s[j] and dp[i + 1][ + j - 1]: # (1) i & j가 가리키는 문자가 서로 같고 (2) 안 쪽 substring이 palindrome 이라면 palindromic substring + dp[i][j] = True + res += 1 + + return res + + def countSubstrings_dp2(self, s: str) -> int: + """ + [Complexity] + - TC: O(n^2) + - SC: O(n^2) + + [Approach] + length <= 2 조건을 or 연산으로 연결함으로써 1), 2), 3) 케이스를 하나로 줄일 수 있다. + """ + + n = len(s) + dp = [[False for _ in range(n)] for _ in range(n)] + res = 0 + + for length in range(1, n + 1): # length는 1부터 n까지 늘려나가기 (**더 짧은 substring에서의 판단 결과를 사용해야 하므로**) + for i in range(n - length + 1): # i = substring 시작 인덱스 + j = i + length - 1 # j = substring 종료 인덱스 + if s[i] == s[j] and (length <= 2 or dp[i + 1][j - 1]): # length <= 2 조건을 or 연산으로 연결 + dp[i][j] = True + res += 1 + + return res + + def countSubstrings(self, s: str) -> int: + """ + [Complexity] + - TC: O(n^2) + - SC: O(1) + + [Approach] + palindromic substring인지 여부를 확인하기 위해, 크게 두 가지 경우에 대해 고려해야 한다. + - 길이가 홀수: len == 1인 단위 substring에서 좌우로 확장 + - 길이가 짝수: len == 2인 단위 substring에서 좌우로 확장 + + 따라서 s를 순회하며, 각 문자를 center로 하여 만들 수 있는 길이가 홀수 & 짝수인 palindrome의 개수를 좌우로 확장해가며 카운트한다. + """ + + def count_palindrome_from_center(lo, hi): + cnt = 0 + + # lo와 hi가 범위를 벗어나지 않도록 + while (lo >= 0 and hi < len(s)): + # lo와 hi가 가리키는 문자가 다르다면 더이상 확장하며 palindrome을 찾을 수 없음 + if s[lo] != s[hi]: + break + + # lo와 hi를 좌우로 확장 + lo -= 1 + hi += 1 + + # palindrome 개수 증가 + cnt += 1 + + return cnt + + res = 0 + + # center 인덱스 순회 + for i in range(len(s)): + # 길이가 홀수: len == 1인 단위 substring에서 좌우로 확장 + res += count_palindrome_from_center(i, i) + + # 길이가 짝수: len == 2인 단위 substring에서 좌우로 확장 + res += count_palindrome_from_center(i, i + 1) + + return res