Skip to content

Commit 72e93b7

Browse files
authored
Merge pull request #609 from lymchgmk/feat/week15
[EGON] Week15 Solutions
2 parents 93acc92 + e02dee1 commit 72e93b7

File tree

5 files changed

+458
-0
lines changed

5 files changed

+458
-0
lines changed

alien-dictionary/EGON.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from collections import deque
2+
from typing import List
3+
from unittest import TestCase, main
4+
5+
6+
class Solution:
7+
def foreignDictionary(self, words: List[str]) -> str:
8+
return self.solve_topological_sort(words)
9+
10+
"""
11+
LintCode 로그인이 안되어서 https://neetcode.io/problems/foreign-dictionary 에서 실행시키고 통과만 확인했습니다.
12+
13+
Runtime: ? ms (Beats ?%)
14+
Time Complexity:
15+
#0. 복잡도 변수 정의
16+
- words 배열의 길이를 n
17+
- words 배열을 이루는 단어들의 평균 길이를 l
18+
- words 배열을 이루는 단어를 이루는 문자들의 총 갯수를 c (= n * l)
19+
- words 배열을 이루는 단어를 이루는 문자들의 중복 제거 집합의 크기를 s라 하자
20+
21+
#1. 초기화
22+
- words 배열을 이루는 단어를 이루는 문자들을 조회하며 char_set을 초기화하는데 O(c)
23+
- 위상정렬에 사용할 graph 딕셔너리 초기화를 위해 char_set의 크기만큼 조회하므로 O(s)
24+
- 마찬가지로 위상정렬에 사용할 rank 딕셔너리 초기화에 O(s)
25+
> O(c) + O(s) + O(s) ~= O(c + s)
26+
27+
#2. 위상정렬 간선 초기화
28+
- words 배열을 조회하는데 O(n - 1)
29+
- 단어 간 접두사 관계인 경우, 체크하는 startswith 메서드 사용에 * O(l)
30+
- 단어 간 접두사 관계가 아닌 경우, first_char, second_char를 구하는데
31+
- zip 생성에 O(l)
32+
- zip 조회에 * O(l)
33+
> O(n - 1) * (O(l) + O(l) * O(l)) ~= O(n) * O(l ^ 2) ~= O(c * l) ~= O(c)
34+
35+
#3. 위상정렬 실행
36+
- dq 초기화에 rank 딕셔너리의 모든 키를 조회하는데 O(s)
37+
- dq를 이용해서 graph 딕셔너리의 모든 values를 순회하는데, #2에서 각 first_char, second_char마다 1회 value가 추가되었으므로, 중복이 없는 경우 최대 O(n), upper bound
38+
> O(s) + O(n) ~= O(s + n), upper bound
39+
40+
#4. 최종 계산
41+
> O(c + s) + O(c) + O(s + n) ~= O(c + s) + O(s + n) = O(n * l + s) + O(n + s) ~= O(n * l + s), upper bound
42+
43+
Memory: ? MB (Beats ?%)
44+
Space Complexity: O(s + c)
45+
- char_set의 크기에서 O(s)
46+
- graph의 keys는 최대 s개이고 values는 최대 c개이므로 O(s + c), upper bound
47+
- rank의 keys의 크기에서 O(s)
48+
- dq의 최대 크기는 rank의 크기와 같으므로 O(s)
49+
> O(s) + O(s + c) + O(s) + O(s) ~= O(s + c)
50+
"""
51+
def solve_topological_sort(self, words: List[str]) -> str:
52+
if not words:
53+
return ""
54+
55+
char_set = set([char for word in words for char in word])
56+
graph = {char: set() for char in char_set}
57+
rank = {char: 0 for char in char_set}
58+
for i in range(len(words) - 1):
59+
first_word, second_word = words[i], words[i + 1]
60+
61+
if len(first_word) > len(second_word) and first_word.startswith(second_word):
62+
return ""
63+
64+
first_char, second_char = next(((fc, sc) for fc, sc in zip(first_word, second_word) if fc != sc), ("", ""))
65+
if (first_char and second_char) and second_char not in graph[first_char]:
66+
graph[first_char].add(second_char)
67+
rank[second_char] += 1
68+
69+
result = []
70+
dq = deque([char for char in rank if rank[char] == 0])
71+
while dq:
72+
curr_char = dq.popleft()
73+
result.append(curr_char)
74+
for post_char in graph[curr_char]:
75+
rank[post_char] -= 1
76+
if rank[post_char] == 0:
77+
dq.append(post_char)
78+
79+
if len(result) != len(rank):
80+
return ""
81+
else:
82+
return "".join(result)
83+
84+
85+
class _LeetCodeTestCases(TestCase):
86+
def test_1(self):
87+
words = ["z","o"]
88+
output = "zo"
89+
solution = Solution()
90+
self.assertEqual(solution.foreignDictionary(words), output)
91+
92+
def test_2(self):
93+
words = ["hrn","hrf","er","enn","rfnn"]
94+
output = "hernf"
95+
solution = Solution()
96+
self.assertEqual(solution.foreignDictionary(words), output)
97+
98+
def test_3(self):
99+
words = ["wrt","wrf","er","ett","rftt","te"]
100+
output = "wertf"
101+
solution = Solution()
102+
self.assertEqual(solution.foreignDictionary(words), output)
103+
104+
105+
if __name__ == '__main__':
106+
main()

longest-palindromic-substring/EGON.py

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from unittest import TestCase, main
2+
3+
4+
class Solution:
5+
def longestPalindrome(self, s: str) -> str:
6+
return self.solve_manacher_algorithm(s)
7+
8+
"""
9+
Runtime: 47 ms (Beats 96.97%)
10+
Time Complexity: O(n ^ 3)
11+
- s의 길이를 n이라 하면, s의 길이 - 1 만큼 조회하는데 O(n - 1)
12+
- 각 문자마다 sliding_window를 2회 호출하는데, 각 호출마다 최대 s의 길이만큼 반복하므로, * 2 * O(n), upper bound
13+
- 반복 후 s를 slicing하는데 최대 * O(n), upper bound
14+
> O(n - 1) * (2 * O(n)) * O(n) ~= O(n ^ 3)
15+
16+
Memory: 16.54 MB (Beats 88.85%)
17+
Space Complexity: O(n)
18+
- sliding_window의 결과로 생성되는 문자열의 최대 길이는 n이고, 조회마다 2회 생성되므로 2 * O(n), upper bound
19+
> 2 * O(n) ~= O(n)
20+
"""
21+
def solve_sliding_window(self, s: str) -> str:
22+
23+
def sliding_window(left: int, right: int) -> str:
24+
while 0 <= left and right < len(s) and s[left] == s[right - 1]:
25+
left -= 1
26+
right += 1
27+
28+
return s[left + 1: right - 1]
29+
30+
if len(s) < 2 or s == s[::-1]:
31+
return s
32+
33+
result = ''
34+
for i in range(len(s) - 1):
35+
result = max(result, sliding_window(i, i + 1), sliding_window(i, i + 2), key=len)
36+
37+
return result
38+
39+
"""
40+
Runtime: 36 ms (Beats 98.09%)
41+
Time Complexity: O(n ^ 2)
42+
- s의 길이를 n이라 하면, s의 길이 - 1 만큼 조회하는데 O(n - 1)
43+
- 각 문자마다 two_pointer 2회 호출하는데, 각 호출마다 최대 s의 길이만큼 반복하므로, * 2 * O(n), upper bound
44+
> O(n - 1) * (2 * O(n)) ~= O(n ^ 2)
45+
46+
Memory: 16.85 MB (Beats 24.42%)
47+
Space Complexity: O(1)
48+
> 모든 변수는 result를 제외하고 인덱스를 위한 정수 변수만 사용하므로 O(1)
49+
"""
50+
def solve_two_pointer(self, s: str) -> str:
51+
52+
if len(s) < 2 or s == s[::-1]:
53+
return s
54+
55+
def two_pointer(left: int, right: int) -> (int, int):
56+
while left >= 0 and right < len(s) and s[left] == s[right]:
57+
left -= 1
58+
right += 1
59+
60+
return left + 1, right - 1
61+
62+
start, end = 0, 0
63+
for i in range(len(s) - 1):
64+
first_left, first_right = two_pointer(i, i)
65+
second_left, second_right = two_pointer(i, i + 1)
66+
67+
if first_right - first_left > end - start:
68+
start, end = first_left, first_right
69+
if second_right - second_left > end - start:
70+
start, end = second_left, second_right
71+
72+
return s[start: end + 1]
73+
74+
"""
75+
Time Complexity: O(n)
76+
Space Complexity: O(n)
77+
"""
78+
def solve_manacher_algorithm(self, s: str) -> str:
79+
SEPARATOR = '@'
80+
# Step 1: Transform the string
81+
t = SEPARATOR + SEPARATOR.join(s) + SEPARATOR
82+
n = len(t)
83+
p = [0] * n
84+
center = right = 0 # Center and right boundary
85+
max_length = 0
86+
max_center = 0
87+
88+
# Step 2: Calculate palindrome radius for each character
89+
for c in range(n):
90+
# Use previously calculated information (symmetry)
91+
if c < right:
92+
p[c] = min(p[2 * center - c], right - c)
93+
94+
# Try to expand around i
95+
while (0 <= c - p[c] - 1 and c + p[c] + 1 < n) and (t[c - p[c] - 1] == t[c + p[c] + 1]):
96+
p[c] += 1
97+
98+
# Update center and right boundary if expanded beyond current right
99+
if c + p[c] > right:
100+
center = c
101+
right = c + p[c]
102+
103+
# Update max palindrome length and center
104+
if p[c] > max_length:
105+
max_length = p[c]
106+
max_center = c
107+
108+
# Step 3: Extract the original string's palindrome
109+
start = (max_center - max_length) // 2
110+
return s[start:start + max_length]
111+
112+
113+
class _LeetCodeTestCases(TestCase):
114+
def test_1(self):
115+
s = "babad"
116+
output = "bab"
117+
self.assertEqual(Solution().longestPalindrome(s), output)
118+
119+
def test_2(self):
120+
s = "cbbd"
121+
output = "bb"
122+
self.assertEqual(Solution().longestPalindrome(s), output)
123+
124+
125+
if __name__ == '__main__':
126+
main()

rotate-image/EGON.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from typing import List
2+
from unittest import TestCase, main
3+
4+
5+
class Solution:
6+
def rotate(self, matrix: List[List[int]]) -> None:
7+
return self.solve(matrix)
8+
9+
"""
10+
Runtime: 0 ms (Beats 100.00%)
11+
Time Complexity: O(n ^ 2)
12+
- 행렬의 행과 열을 교환하기 위해 이중 for문 사용에 O(n ^ 2)
13+
- 행렬의 각 행을 뒤집기 위해, 행을 조회하는데 O(n)
14+
- 각 행을 뒤집는데 * O(n)
15+
> O(n ^ 2) + O(n) * O(n) ~= O(n ^ 2) + O(n ^ 2) ~= O(n ^ 2)
16+
17+
Memory: 16.76 MB (Beats 14.84%)
18+
Space Complexity: O(1)
19+
> in-place 풀이이므로 상수 변수 할당을 제외한 메모리 사용 없음, O(1)
20+
"""
21+
def solve(self, matrix: List[List[int]]) -> None:
22+
N = len(matrix)
23+
24+
for i in range(N):
25+
for j in range(i, N):
26+
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
27+
28+
for row in matrix:
29+
row.reverse()
30+
31+
32+
class _LeetCodeTestCases(TestCase):
33+
def test_1(self):
34+
matrix = [[1,2,3],[4,5,6],[7,8,9]]
35+
output = [[7,4,1],[8,5,2],[9,6,3]]
36+
Solution().rotate(matrix)
37+
self.assertEqual(matrix, output)
38+
39+
def test_2(self):
40+
matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
41+
output = [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
42+
Solution().rotate(matrix)
43+
self.assertEqual(matrix, output)
44+
45+
46+
if __name__ == '__main__':
47+
main()

subtree-of-another-tree/EGON.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from typing import Optional
2+
from unittest import TestCase, main
3+
4+
5+
# Definition for a binary tree node.
6+
class TreeNode:
7+
def __init__(self, val=0, left=None, right=None):
8+
self.val = val
9+
self.left = left
10+
self.right = right
11+
12+
13+
class Solution:
14+
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
15+
return self.solve_dfs(root, subRoot)
16+
17+
"""
18+
Runtime: 35 ms (Beats 90.24%)
19+
Time Complexity: O(n)
20+
- root 트리의 크기를 n이라 하면, root 트리의 모든 노드를 조회하는데 O(n)
21+
- 각 노드마다 is_same_tree 실행하는데, subRoot 트리의 크기를 m이라 하면, 최대 subRoot의 노드의 크기만큼 조회하므로 * O(m)
22+
> O(n) * O(m) ~= O(n * m)
23+
24+
Memory: 17.09 (Beats 9.93%)
25+
Space Complexity: O(n + m)
26+
- stack의 최대 크기는 root 트리가 편향된 경우이며, 이는 root 트리의 노드의 총 갯수와 같으므로 O(n), upper bound
27+
- is_same_tree 함수의 재귀 스택의 최대 깊이는 subRoot 트리가 편향된 경우이며, 이는 subRoot 트리의 노드의 총 갯수와 같으므로 O(m), upper bound
28+
> O(n) + O(m) ~= O(n + m)
29+
"""
30+
def solve_dfs(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
31+
32+
def is_same_tree(p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
33+
if p is None and q is None:
34+
return True
35+
elif (p is not None and q is not None) and (p.val == q.val):
36+
return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right)
37+
else:
38+
return False
39+
40+
result = False
41+
stack = [root]
42+
while stack:
43+
curr = stack.pop()
44+
if (curr and subRoot) and curr.val == subRoot.val:
45+
result = result or is_same_tree(curr, subRoot)
46+
47+
if curr.left:
48+
stack.append(curr.left)
49+
50+
if curr.right:
51+
stack.append(curr.right)
52+
53+
return result
54+
55+
56+
class _LeetCodeTestCases(TestCase):
57+
def test_1(self):
58+
root = TreeNode(3)
59+
root_1 = TreeNode(4)
60+
root_2 = TreeNode(5)
61+
root.left = root_1
62+
root.right = root_2
63+
root_3 = TreeNode(1)
64+
root_4 = TreeNode(2)
65+
root.left.left = root_3
66+
root.left.right = root_4
67+
68+
subRoot = TreeNode(4)
69+
sub_1 = TreeNode(1)
70+
sub_2 = TreeNode(2)
71+
subRoot.left = sub_1
72+
subRoot.right = sub_2
73+
74+
output = True
75+
self.assertEqual(Solution.isSubtree(Solution(), root, subRoot), output)
76+
77+
def test_2(self):
78+
root = TreeNode(3)
79+
root_1 = TreeNode(4)
80+
root_2 = TreeNode(5)
81+
root.left = root_1
82+
root.right = root_2
83+
root_3 = TreeNode(1)
84+
root_4 = TreeNode(2)
85+
root.left.left = root_3
86+
root.left.right = root_4
87+
root_5 = TreeNode(0)
88+
root_4.left = root_5
89+
90+
subRoot = TreeNode(4)
91+
sub_1 = TreeNode(1)
92+
sub_2 = TreeNode(2)
93+
subRoot.left = sub_1
94+
subRoot.right = sub_2
95+
96+
output = False
97+
self.assertEqual(Solution.isSubtree(Solution(), root, subRoot), output)
98+
99+
def test_3(self):
100+
root = TreeNode(1)
101+
root.right = TreeNode(1)
102+
root.right.right = TreeNode(1)
103+
root.right.right.right = TreeNode(1)
104+
root.right.right.right.right = TreeNode(1)
105+
root.right.right.right.right.right = TreeNode(2)
106+
107+
subRoot = TreeNode(1)
108+
subRoot.right = TreeNode(1)
109+
subRoot.right.right = TreeNode(2)
110+
111+
output = True
112+
self.assertEqual(Solution.isSubtree(Solution(), root, subRoot), output)
113+
114+
115+
if __name__ == '__main__':
116+
main()

0 commit comments

Comments
 (0)