-
-
Notifications
You must be signed in to change notification settings - Fork 195
[SunaDu] Week 15 #1113
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
[SunaDu] Week 15 #1113
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
''' | ||
# 5. Longest Palindromic Substring | ||
|
||
DP 테이블을 사용하여 팰린드롬 여부를 저장하고 최대 길이를 찾기. | ||
''' | ||
class Solution: | ||
''' | ||
TC: O(n^2) | ||
SC: O(n^2) | ||
''' | ||
def longestPalindrome(self, s: str) -> str: | ||
n = len(s) | ||
if n == 1: | ||
return s | ||
|
||
start, length = 0, 1 | ||
dp = [[False] * n for _ in range(n)] # SC: O(n^2) | ||
|
||
# length 1, diagonal elements in 2d array | ||
for i in range(n): # TC: O(n) | ||
dp[i][i] = True | ||
|
||
# length 2, are two elements same | ||
for i in range(n - 1): | ||
if s[i] == s[i + 1]: | ||
dp[i][i + 1] = True | ||
start, length = i, 2 | ||
|
||
# length 3+ | ||
for word_length in range(3, n + 1): # TC: O(n^2) | ||
for i in range(n - word_length + 1): | ||
j = i + word_length - 1 | ||
|
||
if s[i] == s[j] and dp[i + 1][j - 1]: | ||
dp[i][j] = True | ||
start, length = i, word_length | ||
|
||
return s[start:start + length] | ||
|
||
''' | ||
## Check Leetcode Hints | ||
- How can we reuse a previously computed palindrome to compute a larger palindrome? | ||
- use dp table that stores isPalindrome computation. | ||
- If “aba” is a palindrome, is “xabax” a palindrome? Similarly is “xabay” a palindrome? | ||
- if it is palindrome, we only need to check the outermost chars, that wrapping our palindrome. | ||
- Palindromic checks can be O(1) by reusing prev computations. | ||
- DP! | ||
|
||
## 동적 프로그래밍 풀이 | ||
- s[i:j]의 팰린드롬 여부를 저장하기 위해서는 2차원 배열을 사용하는 것이 간편하다. | ||
- 길이가 1인 경우, 팰린드롬 | ||
- 길이가 2인 경우, 두 문자가 같다면 팰린드롬 | ||
- 3 이상은 dp 테이블을 활용하며 확인 | ||
- ex) 길이가 5인 문자열을 다음과 같이 탐색 | ||
``` | ||
len: 3, i: 0, j: 2 | ||
len: 3, i: 1, j: 3 | ||
len: 3, i: 2, j: 4 | ||
len: 4, i: 0, j: 3 | ||
len: 4, i: 1, j: 4 | ||
len: 5, i: 0, j: 4 | ||
``` | ||
|
||
## 탐구 | ||
- can we use sliding window to optimize? | ||
슬라이딩 윈도우는 연속적인 구간을 유지하며 최적해를 찾을 때 유용하지만, 팰린드롬은 중앙 확장과 이전 계산 재사용이 핵심. | ||
|
||
- can we solve this by counting char? | ||
문자 개수를 세면 "어떤 문자가 몇 번 나왔는지"만 알 수 있지만, 팰린드롬 여부는 문자 순서가 중요하다. | ||
|
||
- 그렇다면 개선 방법은? | ||
> Manacher's Algorithm: https://www.geeksforgeeks.org/manachers-algorithm-linear-time-longest-palindromic-substring-part-1/ | ||
> | ||
>팰린드롬의 대칭성을 활용해 중앙을 기준으로 확장하는 Manacher's Algorithm을 적용할 수 있다. 모든 문자 사이에 #을 넣어 짝수, 홀수를 동일하게 다루는 기법인데 구현이 다소 복잡하다. 시간 복잡도는 O(n)이다. | ||
''' | ||
class Solution: | ||
''' | ||
TC: O(n) | ||
SC: O(n) | ||
''' | ||
def longestPalindromeManacher(self, s: str) -> str: | ||
''' | ||
1. 문자열 변환하기 (가운데에 # 추가) | ||
2. 저장공간 초기화 | ||
3. 변환된 문자열 순회하기 | ||
(a) 미러 인덱스 계산 | ||
(b) 팰린드롬 반지름 확장 | ||
- i에서 양쪽 문자들을 비교해서 확장 | ||
(c) 새로운 팰린드롬 찾기 | ||
- 새로운 팰린드롬을 i로 설정 | ||
- 새로운 팰린드롬의 오른쪽 끝을 i + p[i]로 설정 | ||
(d) 가장 긴 팰린드롬 업데이트 | ||
''' | ||
transformed = '#' + '#'.join(s) + '#' | ||
n = len(transformed) | ||
p = [0] * n # i 중심의 팰린드롬 크기 | ||
c = 0 # 현재 팰린드롬의 중심 | ||
r = 0 # 현재 팰린드롬의 오른쪽 끝 | ||
max_len = 0 # 가장 긴 팰린드롬의 길이 | ||
center = 0 # 가장 긴 팰린드롬의 중심 | ||
|
||
for i in range(n): | ||
# 현재 위치 i의 미러 인덱스 (중앙을 기준으로 대칭되는 인덱스) | ||
# ex) ababbaa | ||
# 0123456 | ||
# i가 3이고 center가 2일 때, 2*2 - 3 = 1, 미러 인덱스는 1 | ||
# i가 5이고 center가 4일 때, 2*4 - 5 = 3, 미러 인덱스는 3 | ||
mirror = 2 * c - i | ||
|
||
if i < r: | ||
# r - i: 얼마나 더 확장 될 수 있는가 => 현재 팰린드롬의 오른쪽 끝에서 현재 인덱스까지의 거리 | ||
# p[mirror]: 미러 인덱스에서의 팰린드롬 반지름 | ||
p[i] = min(r - i, p[mirror]) | ||
# 작은 값만큼만 확장이 가능하다. 예를 들어, r - i가 더 작은 값이라면 팰린드롬을 그만큼만 확장할 수 있고, p[mirror]가 더 작은 값이라면 이미 그만큼 확장이 된 상태 | ||
# r - i가 3이고 p[mirror]가 2라면 팰린드롬을 2만큼 확장할 수 있고, r - i가 2이고 p[mirror]가 3이라면 팰린드롬을 2만큼 확장할 수 있다. | ||
|
||
# 현재 중심에서 팰린드롬을 확장 | ||
# 양 끝이 같다면 팰린드롬 반지름 p[i]를 1씩 증가 | ||
while i + p[i] + 1 < n and i - p[i] - 1 >= 0 and transformed[i + p[i] + 1] == transformed[i - p[i] - 1]: | ||
p[i] += 1 | ||
|
||
# 기존에 찾은 팰린드롬보다 더 큰 팰린드롬을 찾은 경우 | ||
# 현재 중심과 오른쪽 끝을 설정 | ||
if i + p[i] > r: | ||
c = i | ||
r = i + p[i] | ||
|
||
# 가장 긴 팰린드롬 업데이트 | ||
if p[i] > max_len: | ||
max_len = p[i] | ||
center = i | ||
|
||
# 변환된 문자열에서 가장 긴 팰린드롬을 원래 문자열에서 추출 | ||
start = (center - max_len) // 2 | ||
return s[start:start + max_len] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
''' | ||
# 48. Rotate Image | ||
|
||
rotate 2d matrix 90 degree in place. | ||
👉 transpose matrix and reverse each row. | ||
|
||
- original matrix | ||
1 2 3 | ||
4 5 6 | ||
7 8 9 | ||
|
||
- transpose matrix (swap i, j) (flip diagonally) | ||
1 4 7 | ||
2 5 8 | ||
3 6 9 | ||
|
||
- reverse each row (horizontal flip) | ||
7 4 1 | ||
8 5 2 | ||
9 6 3 | ||
''' | ||
class Solution: | ||
''' | ||
TC: O(n^2) | ||
SC: O(1) | ||
''' | ||
def rotate(self, matrix: List[List[int]]) -> None: | ||
n = len(matrix) | ||
|
||
for i in range(n): | ||
for j in range(i, n): | ||
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] | ||
|
||
for i in range(n): | ||
matrix[i].reverse() | ||
|
||
''' | ||
# 💡 other rotate in-place examples | ||
|
||
## rotate 180 degree | ||
|
||
### solution A. reverse each column (vertical flip) & reverse each row (horizontal flip) | ||
|
||
```python | ||
def rotate180(matrix: List[List[int]]) -> None: | ||
n = len(matrix) | ||
matrix.reverse() | ||
|
||
for i in range(n): | ||
matrix[i].reverse() | ||
``` | ||
|
||
### solution B. (after transpose, reverse each row (horizontal flip)) * 2. | ||
|
||
90 degree * 2 = 180 degree | ||
|
||
```python | ||
def rotate180(matrix: List[List[int]]) -> None: | ||
rotate90(matrix) | ||
rotate90(matrix) | ||
``` | ||
|
||
## rotate -90 degree | ||
|
||
after transpose, reverse each column (vertical flip) | ||
|
||
```python | ||
def rotate90CCW(matrix): | ||
n = len(matrix) | ||
|
||
for i in range(n): | ||
for j in range(i, n): | ||
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] | ||
|
||
matrix.reverse() | ||
``` | ||
''' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
''' | ||
# 572. Subtree of Another Tree | ||
|
||
## 1. Recursive | ||
use a recursive function to check if the two trees are identical. | ||
- if they are identical, return True. | ||
- if they are not identical, recursively check the left subtree and right subtree. | ||
|
||
### base case | ||
- isSubtree | ||
- if the current tree is None, return False. | ||
- if the subRoot is None, return True. (empty tree is a subtree of any tree) | ||
- isIdentical | ||
- if both trees are None, return True. (they are identical) | ||
- if one of the trees is None, return False. | ||
- if the values of the current nodes are different, return False. | ||
- left subtree and right subtree's results are must be True. | ||
|
||
## 2. Snapshot | ||
> reference: https://www.algodale.com/problems/subtree-of-another-tree/ | ||
|
||
use a snapshot string of the preorder traversal to check if the subRoot is a subtree of the current tree. | ||
- if the subRoot is a subtree of the current tree, return True. | ||
- if the subRoot is not a subtree of the current tree, return False. | ||
''' | ||
class Solution: | ||
''' | ||
1. Recursive | ||
TC: O(n * m), m is the number of nodes in the subRoot. | ||
SC: O(n) (recursive stack space) | ||
''' | ||
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool: | ||
if not root: | ||
return False | ||
if not subRoot: | ||
return True | ||
|
||
if self.isIdentical(root, subRoot): | ||
return True | ||
|
||
return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot) | ||
|
||
def isIdentical(self, s: Optional[TreeNode], t: Optional[TreeNode]) -> bool: | ||
if not s and not t: | ||
return True | ||
if not s or not t: | ||
return False | ||
|
||
return s.val == t.val and self.isIdentical(s.left, t.left) and self.isIdentical(s.right, t.right) | ||
|
||
''' | ||
2. Snapshot | ||
TC: O(n + m), n is the number of nodes in the root, m is the number of nodes in the subRoot. | ||
SC: O(n + m) (recursive stack space) | ||
''' | ||
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool: | ||
def preorder(node): | ||
if not node: | ||
return '#' | ||
return f'({node.val},{preorder(node.left)},{preorder(node.right)})' | ||
|
||
return preorder(subRoot) in preorder(root) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
''' | ||
# 98. Validate Binary Search Tree | ||
|
||
check if is a valid BST. | ||
- the left value is smaller than the current value | ||
- the right value is greater than the current value | ||
|
||
👉 all left and right subtrees must also valid continuously. | ||
so, we need to check the low & high bound recursively. (not only check current node & immediate children) | ||
|
||
## base case | ||
- if the node is None, return True (empty tree is valid BST) | ||
- valid node value has range of left < node.val < right, if not, return False | ||
- recursively check the left and right subtree, update the low & high. | ||
''' | ||
class Solution: | ||
def isValidBST(self, root: Optional[TreeNode]) -> bool: | ||
def dfs(node, low = float('-inf'), high = float('inf')): | ||
if not node: | ||
return True | ||
if not (low < node.val < high): | ||
return False | ||
|
||
return dfs(node.left, low, node.val) and dfs(node.right, node.val, high) | ||
|
||
return dfs(root) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
선아님 안녕하세요!
Brute force가 아닌 다른 방법을 고민하고 있었는데, 주석을 정말 자세하게 달아주셔서 공부하는 데에 도움이 많이 됐습니다 😄👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요 나연님~
풀고 개선하는 데 시간이 굉장히 많이 걸렸어요😅
개인적으로 15주 동안 푼 문제 중에서 손꼽히게 어려웠던 것 같아요.
도움이 되었다니 다행입니다!