Skip to content

Commit 117c6d4

Browse files
authored
Merge pull request #1113 from dusunax/main
[SunaDu] Week 15
2 parents 2f110e0 + e3b5bde commit 117c6d4

File tree

4 files changed

+300
-0
lines changed

4 files changed

+300
-0
lines changed
+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
'''
2+
# 5. Longest Palindromic Substring
3+
4+
DP 테이블을 사용하여 팰린드롬 여부를 저장하고 최대 길이를 찾기.
5+
'''
6+
class Solution:
7+
'''
8+
TC: O(n^2)
9+
SC: O(n^2)
10+
'''
11+
def longestPalindrome(self, s: str) -> str:
12+
n = len(s)
13+
if n == 1:
14+
return s
15+
16+
start, length = 0, 1
17+
dp = [[False] * n for _ in range(n)] # SC: O(n^2)
18+
19+
# length 1, diagonal elements in 2d array
20+
for i in range(n): # TC: O(n)
21+
dp[i][i] = True
22+
23+
# length 2, are two elements same
24+
for i in range(n - 1):
25+
if s[i] == s[i + 1]:
26+
dp[i][i + 1] = True
27+
start, length = i, 2
28+
29+
# length 3+
30+
for word_length in range(3, n + 1): # TC: O(n^2)
31+
for i in range(n - word_length + 1):
32+
j = i + word_length - 1
33+
34+
if s[i] == s[j] and dp[i + 1][j - 1]:
35+
dp[i][j] = True
36+
start, length = i, word_length
37+
38+
return s[start:start + length]
39+
40+
'''
41+
## Check Leetcode Hints
42+
- How can we reuse a previously computed palindrome to compute a larger palindrome?
43+
- use dp table that stores isPalindrome computation.
44+
- If “aba” is a palindrome, is “xabax” a palindrome? Similarly is “xabay” a palindrome?
45+
- if it is palindrome, we only need to check the outermost chars, that wrapping our palindrome.
46+
- Palindromic checks can be O(1) by reusing prev computations.
47+
- DP!
48+
49+
## 동적 프로그래밍 풀이
50+
- s[i:j]의 팰린드롬 여부를 저장하기 위해서는 2차원 배열을 사용하는 것이 간편하다.
51+
- 길이가 1인 경우, 팰린드롬
52+
- 길이가 2인 경우, 두 문자가 같다면 팰린드롬
53+
- 3 이상은 dp 테이블을 활용하며 확인
54+
- ex) 길이가 5인 문자열을 다음과 같이 탐색
55+
```
56+
len: 3, i: 0, j: 2
57+
len: 3, i: 1, j: 3
58+
len: 3, i: 2, j: 4
59+
len: 4, i: 0, j: 3
60+
len: 4, i: 1, j: 4
61+
len: 5, i: 0, j: 4
62+
```
63+
64+
## 탐구
65+
- can we use sliding window to optimize?
66+
슬라이딩 윈도우는 연속적인 구간을 유지하며 최적해를 찾을 때 유용하지만, 팰린드롬은 중앙 확장과 이전 계산 재사용이 핵심.
67+
68+
- can we solve this by counting char?
69+
문자 개수를 세면 "어떤 문자가 몇 번 나왔는지"만 알 수 있지만, 팰린드롬 여부는 문자 순서가 중요하다.
70+
71+
- 그렇다면 개선 방법은?
72+
> Manacher's Algorithm: https://www.geeksforgeeks.org/manachers-algorithm-linear-time-longest-palindromic-substring-part-1/
73+
>
74+
>팰린드롬의 대칭성을 활용해 중앙을 기준으로 확장하는 Manacher's Algorithm을 적용할 수 있다. 모든 문자 사이에 #을 넣어 짝수, 홀수를 동일하게 다루는 기법인데 구현이 다소 복잡하다. 시간 복잡도는 O(n)이다.
75+
'''
76+
class Solution:
77+
'''
78+
TC: O(n)
79+
SC: O(n)
80+
'''
81+
def longestPalindromeManacher(self, s: str) -> str:
82+
'''
83+
1. 문자열 변환하기 (가운데에 # 추가)
84+
2. 저장공간 초기화
85+
3. 변환된 문자열 순회하기
86+
(a) 미러 인덱스 계산
87+
(b) 팰린드롬 반지름 확장
88+
- i에서 양쪽 문자들을 비교해서 확장
89+
(c) 새로운 팰린드롬 찾기
90+
- 새로운 팰린드롬을 i로 설정
91+
- 새로운 팰린드롬의 오른쪽 끝을 i + p[i]로 설정
92+
(d) 가장 긴 팰린드롬 업데이트
93+
'''
94+
transformed = '#' + '#'.join(s) + '#'
95+
n = len(transformed)
96+
p = [0] * n # i 중심의 팰린드롬 크기
97+
c = 0 # 현재 팰린드롬의 중심
98+
r = 0 # 현재 팰린드롬의 오른쪽 끝
99+
max_len = 0 # 가장 긴 팰린드롬의 길이
100+
center = 0 # 가장 긴 팰린드롬의 중심
101+
102+
for i in range(n):
103+
# 현재 위치 i의 미러 인덱스 (중앙을 기준으로 대칭되는 인덱스)
104+
# ex) ababbaa
105+
# 0123456
106+
# i가 3이고 center가 2일 때, 2*2 - 3 = 1, 미러 인덱스는 1
107+
# i가 5이고 center가 4일 때, 2*4 - 5 = 3, 미러 인덱스는 3
108+
mirror = 2 * c - i
109+
110+
if i < r:
111+
# r - i: 얼마나 더 확장 될 수 있는가 => 현재 팰린드롬의 오른쪽 끝에서 현재 인덱스까지의 거리
112+
# p[mirror]: 미러 인덱스에서의 팰린드롬 반지름
113+
p[i] = min(r - i, p[mirror])
114+
# 작은 값만큼만 확장이 가능하다. 예를 들어, r - i가 더 작은 값이라면 팰린드롬을 그만큼만 확장할 수 있고, p[mirror]가 더 작은 값이라면 이미 그만큼 확장이 된 상태
115+
# r - i가 3이고 p[mirror]가 2라면 팰린드롬을 2만큼 확장할 수 있고, r - i가 2이고 p[mirror]가 3이라면 팰린드롬을 2만큼 확장할 수 있다.
116+
117+
# 현재 중심에서 팰린드롬을 확장
118+
# 양 끝이 같다면 팰린드롬 반지름 p[i]를 1씩 증가
119+
while i + p[i] + 1 < n and i - p[i] - 1 >= 0 and transformed[i + p[i] + 1] == transformed[i - p[i] - 1]:
120+
p[i] += 1
121+
122+
# 기존에 찾은 팰린드롬보다 더 큰 팰린드롬을 찾은 경우
123+
# 현재 중심과 오른쪽 끝을 설정
124+
if i + p[i] > r:
125+
c = i
126+
r = i + p[i]
127+
128+
# 가장 긴 팰린드롬 업데이트
129+
if p[i] > max_len:
130+
max_len = p[i]
131+
center = i
132+
133+
# 변환된 문자열에서 가장 긴 팰린드롬을 원래 문자열에서 추출
134+
start = (center - max_len) // 2
135+
return s[start:start + max_len]

rotate-image/dusunax.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
'''
2+
# 48. Rotate Image
3+
4+
rotate 2d matrix 90 degree in place.
5+
👉 transpose matrix and reverse each row.
6+
7+
- original matrix
8+
1 2 3
9+
4 5 6
10+
7 8 9
11+
12+
- transpose matrix (swap i, j) (flip diagonally)
13+
1 4 7
14+
2 5 8
15+
3 6 9
16+
17+
- reverse each row (horizontal flip)
18+
7 4 1
19+
8 5 2
20+
9 6 3
21+
'''
22+
class Solution:
23+
'''
24+
TC: O(n^2)
25+
SC: O(1)
26+
'''
27+
def rotate(self, matrix: List[List[int]]) -> None:
28+
n = len(matrix)
29+
30+
for i in range(n):
31+
for j in range(i, n):
32+
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
33+
34+
for i in range(n):
35+
matrix[i].reverse()
36+
37+
'''
38+
# 💡 other rotate in-place examples
39+
40+
## rotate 180 degree
41+
42+
### solution A. reverse each column (vertical flip) & reverse each row (horizontal flip)
43+
44+
```python
45+
def rotate180(matrix: List[List[int]]) -> None:
46+
n = len(matrix)
47+
matrix.reverse()
48+
49+
for i in range(n):
50+
matrix[i].reverse()
51+
```
52+
53+
### solution B. (after transpose, reverse each row (horizontal flip)) * 2.
54+
55+
90 degree * 2 = 180 degree
56+
57+
```python
58+
def rotate180(matrix: List[List[int]]) -> None:
59+
rotate90(matrix)
60+
rotate90(matrix)
61+
```
62+
63+
## rotate -90 degree
64+
65+
after transpose, reverse each column (vertical flip)
66+
67+
```python
68+
def rotate90CCW(matrix):
69+
n = len(matrix)
70+
71+
for i in range(n):
72+
for j in range(i, n):
73+
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
74+
75+
matrix.reverse()
76+
```
77+
'''

subtree-of-another-tree/dusunax.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
'''
2+
# 572. Subtree of Another Tree
3+
4+
## 1. Recursive
5+
use a recursive function to check if the two trees are identical.
6+
- if they are identical, return True.
7+
- if they are not identical, recursively check the left subtree and right subtree.
8+
9+
### base case
10+
- isSubtree
11+
- if the current tree is None, return False.
12+
- if the subRoot is None, return True. (empty tree is a subtree of any tree)
13+
- isIdentical
14+
- if both trees are None, return True. (they are identical)
15+
- if one of the trees is None, return False.
16+
- if the values of the current nodes are different, return False.
17+
- left subtree and right subtree's results are must be True.
18+
19+
## 2. Snapshot
20+
> reference: https://www.algodale.com/problems/subtree-of-another-tree/
21+
22+
use a snapshot string of the preorder traversal to check if the subRoot is a subtree of the current tree.
23+
- if the subRoot is a subtree of the current tree, return True.
24+
- if the subRoot is not a subtree of the current tree, return False.
25+
'''
26+
class Solution:
27+
'''
28+
1. Recursive
29+
TC: O(n * m), m is the number of nodes in the subRoot.
30+
SC: O(n) (recursive stack space)
31+
'''
32+
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
33+
if not root:
34+
return False
35+
if not subRoot:
36+
return True
37+
38+
if self.isIdentical(root, subRoot):
39+
return True
40+
41+
return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
42+
43+
def isIdentical(self, s: Optional[TreeNode], t: Optional[TreeNode]) -> bool:
44+
if not s and not t:
45+
return True
46+
if not s or not t:
47+
return False
48+
49+
return s.val == t.val and self.isIdentical(s.left, t.left) and self.isIdentical(s.right, t.right)
50+
51+
'''
52+
2. Snapshot
53+
TC: O(n + m), n is the number of nodes in the root, m is the number of nodes in the subRoot.
54+
SC: O(n + m) (recursive stack space)
55+
'''
56+
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
57+
def preorder(node):
58+
if not node:
59+
return '#'
60+
return f'({node.val},{preorder(node.left)},{preorder(node.right)})'
61+
62+
return preorder(subRoot) in preorder(root)
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'''
2+
# 98. Validate Binary Search Tree
3+
4+
check if is a valid BST.
5+
- the left value is smaller than the current value
6+
- the right value is greater than the current value
7+
8+
👉 all left and right subtrees must also valid continuously.
9+
so, we need to check the low & high bound recursively. (not only check current node & immediate children)
10+
11+
## base case
12+
- if the node is None, return True (empty tree is valid BST)
13+
- valid node value has range of left < node.val < right, if not, return False
14+
- recursively check the left and right subtree, update the low & high.
15+
'''
16+
class Solution:
17+
def isValidBST(self, root: Optional[TreeNode]) -> bool:
18+
def dfs(node, low = float('-inf'), high = float('inf')):
19+
if not node:
20+
return True
21+
if not (low < node.val < high):
22+
return False
23+
24+
return dfs(node.left, low, node.val) and dfs(node.right, node.val, high)
25+
26+
return dfs(root)

0 commit comments

Comments
 (0)