Skip to content

Commit

Permalink
Merge pull request #595 from lymchgmk/feat/week14
Browse files Browse the repository at this point in the history
[EGON] Week14 Solutions
  • Loading branch information
SamTheKorean authored Nov 17, 2024
2 parents d0b74d1 + d0ebb69 commit fad730e
Show file tree
Hide file tree
Showing 5 changed files with 388 additions and 0 deletions.
108 changes: 108 additions & 0 deletions binary-tree-level-order-traversal/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from collections import deque
from typing import Optional, List
from unittest import TestCase, main


# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right


class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
return self.solve_dfs(root)

"""
Runtime: 0 ms (Beats 100.00%)
Time Complexity: O(n)
> 각 node를 bfs 방식으로 한 번 씩 조회하므로 O(n)
Memory: 17.42 (Beats 11.96%)
Space Complexity: O(n)
> 최악의 경우, deque에 최대 node의 갯수만큼 저장될 수 있으므로 O(n), upper bound
"""
def solve_bfs(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []

dq = deque([root])
result = []
while dq:
tmp_result = []
for _ in range(len(dq)):
curr = dq.popleft()
tmp_result.append(curr.val)

if curr.left:
dq.append(curr.left)
if curr.right:
dq.append(curr.right)

result.append(tmp_result)

return result

"""
Runtime: 1 ms (Beats 38.71%)
Time Complexity: O(n)
> 각 node를 dfs 방식으로 한 번 씩 조회하므로 O(n)
Memory: 17.49 (Beats 11.96%)
Space Complexity: O(1)
> 최악의 경우, list에 최대 node의 갯수만큼 저장될 수 있으므로 O(n), upper bound
"""
def solve_dfs(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []

def dfs(node: TreeNode, depth: int):
if len(result) <= depth:
result.append([node.val])
else:
result[depth].append(node.val)

if node.left:
dfs(node.left, depth + 1)
if node.right:
dfs(node.right, depth + 1)

result = []
dfs(root, 0)

return result


class _LeetCodeTestCases(TestCase):

def test_1(self):
root = TreeNode(3)
node1 = TreeNode(9)
node2 = TreeNode(20)
node3 = TreeNode(15)
node4 = TreeNode(7)
root.left = node1
root.right = node2
node2.left = node3
node2.right = node4
output = [[3], [9, 20], [15, 7]]
self.assertEqual(Solution.levelOrder(Solution(), root), output)

def test_2(self):
root = TreeNode(1)
node1 = TreeNode(2)
node2 = TreeNode(3)
node3 = TreeNode(4)
node4 = TreeNode(5)
root.left = node1
root.right = node2
node1.left = node3
node2.left = node4
output = [[1], [2, 3], [4, 5]]
self.assertEqual(Solution.levelOrder(Solution(), root), output)


if __name__ == '__main__':
main()
60 changes: 60 additions & 0 deletions house-robber-ii/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from collections import deque
from typing import List
from unittest import TestCase, main


class Solution:
def rob(self, nums: List[int]) -> int:
return self.solve_dp(nums)

"""
Runtime: 0 ms (Beats 100.00%)
Time Complexity: O(n)
- 인덱스가 0인 곳을 도둑질한 경우에 대한 dp1에서, 인덱스 2부터 n-2까지 조회하므로 O(n - 3)
- 각 조회마다 2항 max 연산을 2회씩 하므로 * O(2 * 2)
- 인덱스가 0인 곳을 도둑질하지 않는 경우에 대한 dp2에서, 인덱스 2부터 n-1까지 조회하므로 O(n - 2)
- 각 조회마다 2항 max 연산을 2회씩 하므로 * O(2 * 2)
- 그 외에 정해진 횟수의 max 연산들은 무시, O(C)
> O(n - 3) * O(2 * 2) + O(n - 4) * O(2 * 2) + O(C) ~= O(n) * O(4) ~= O(n)
Memory: 16.59 (Beats 62.16%)
Space Complexity: O(n)
- dp1과 dp2가 각각 nums의 길이와 같으므로 O(n * 2)
> O(n * 2) ~= O(n)
"""
def solve_dp(self, nums: List[int]) -> int:
if len(nums) <= 3:
return max(nums)

dp1 = [0] * len(nums)
dp1[0], dp1[1] = nums[0], max(nums[0], nums[1])
max_dp1 = max(dp1[0], dp1[1])
for i in range(2, len(nums) - 1):
dp1[i] = max(dp1[i - 1], dp1[i - 2] + nums[i])
max_dp1 = max(max_dp1, dp1[i])

dp2 = [0] * len(nums)
dp2[0], dp2[1] = 0, nums[1]
max_dp2 = max(dp2[0], dp2[1])
for i in range(2, len(nums)):
dp2[i] = max(dp2[i - 1], dp2[i - 2] + nums[i])
max_dp2 = max(max_dp2, dp2[i])

return max(max_dp1, max_dp2)


class _LeetCodeTestCases(TestCase):

def test_1(self):
nums = [2, 3, 2]
output = 3
self.assertEqual(Solution.rob(Solution(), nums), output)

def test_2(self):
nums = [1, 2, 3, 1]
output = 4
self.assertEqual(Solution.rob(Solution(), nums), output)


if __name__ == '__main__':
main()
74 changes: 74 additions & 0 deletions meeting-rooms-ii/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from typing import List
from unittest import TestCase, main


# Definition of Interval:
class Interval(object):
def __init__(self, start, end):
self.start = start
self.end = end


class Solution:
def minMeetingRooms(self, intervals: List[Interval]) -> int:
return self.solve_two_pointer(intervals)

"""
LintCode 로그인이 안되어서 https://neetcode.io/problems/meeting-schedule-ii 에서 실행시키고 통과만 확인했습니다.
Runtime: ? ms (Beats ?%)
Time Complexity: O(n log n)
- intervals의 길이를 n이라 하면, starts를 정렬하는데 O(n log n)
- ends를 정렬하는데 O(n log n)
- intervals를 인덱스를 이용해 전체 조회하는데 O(n)
> O(n log n) * 2 + O(n) ~= O(n log n)
Memory: ? MB (Beats ?%)
Space Complexity: O(n)
- starts와 ends의 크기는 intervals와 같으므로 O(n)
- 포인터로 index를 사용했으므로 O(1)
> O(n) + O(1) ~= O(n)
"""
def solve_two_pointer(self, intervals: List[Interval]) -> int:
if not intervals:
return 0

starts, ends = sorted([i.start for i in intervals]), sorted([i.end for i in intervals])
start_idx, end_idx = 0, 0
schedule_count = 0
while start_idx < len(intervals):
if starts[start_idx] < ends[end_idx]:
schedule_count += 1
start_idx += 1
else:
end_idx += 1
start_idx += 1

return schedule_count


class _LeetCodeTestCases(TestCase):
def test_1(self):
intervals = [Interval(0,40), Interval(5,10), Interval(15,20)]
output = 2
self.assertEqual(Solution().minMeetingRooms(intervals), output)

def test_2(self):
intervals = [Interval(4, 9)]
output = 1
self.assertEqual(Solution().minMeetingRooms(intervals), output)

def test_3(self):
intervals = [
Interval(1, 5),
Interval(2, 6),
Interval(3, 7),
Interval(4, 8),
Interval(5, 9),
]
output = 4
self.assertEqual(Solution().minMeetingRooms(intervals), output)


if __name__ == '__main__':
main()
34 changes: 34 additions & 0 deletions reverse-bits/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from unittest import TestCase, main


class Solution:
def reverseBits(self, n: int) -> int:
return self.solve(n)

"""
Runtime: 32 ms (Beats 80.50%)
Time Complexity: O(1)
- n을 str로 변환하는데, n은 32 bit 정수이므로 O(32), upper bound
- zfill로 문자열의 길이를 32로 맞추는데, O(32), upper bound
- 문자열을 뒤집는데 마찬가지로, O(32), upper bound
- 뒤집은 문자열을 정수로 변환하는데 문자열에 비례하며 이 길이는 최대 32이므로, O(32), upper bound
> O(32) * O(32) * O(32) * O(32) ~= O(1)
Memory: 16.50 (Beats 64.72%)
Space Complexity: O(1)
- 각 단계마다 최대 길이가 32인 문자열이 임시로 저장되므로 O(32) * 4
> O(32) * 4 ~= O(1)
"""
def solve(self, n: int) -> int:
return int(str(n).zfill(32)[::-1], 2)


class _LeetCodeTestCases(TestCase):
def test_1(self):
n = int("00000010100101000001111010011100")
output = 964176192
self.assertEqual(Solution.reverseBits(Solution(), n), output)


if __name__ == '__main__':
main()
112 changes: 112 additions & 0 deletions word-search-ii/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from typing import List
from unittest import TestCase, main


class Node:

def __init__(self, key, data=None):
self.key = key
self.data = data
self.children = {}


class Trie:

def __init__(self):
self.root = Node(None)

def insert(self, word: str) -> None:
curr_node = self.root
for char in word:
if char not in curr_node.children:
curr_node.children[char] = Node(char)

curr_node = curr_node.children[char]

curr_node.data = word


class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
return self.solve_trie_dfs(board, words)

"""
* Constraints:
1. m == board.length
2. n == board[i].length
3. 1 <= m, n <= 12
4. board[i][j] is a lowercase English letter.
5. 1 <= words.length <= 3 * 104
6. 1 <= words[i].length <= 10
7. words[i] consists of lowercase English letters.
8. All the strings of words are unique.
Runtime: 6439 ms (Beats 26.38%)
Time Complexity: O(R * C * (4 ^ max L))
- word의 최대 길이를 max L, words의 길이를 K라 하면, trie에 words를 모두 insert하는데 O(max L * K), upper bound
- board의 각 grid에서 조회하는데 O(R * C)
- grid마다 dfs 호출하는데, dfs의 방향은 4곳이고, 호출 스택의 최대 깊이는 max L 이므로, * O(4 ^ max L)
> O(max L * K) + O(R * C) * O(4 ^ max L) ~= O(R * C * (4 ^ max L))
Memory: 19.04 MB (Beats 20.79%)
Space Complexity: O(max L * K)
- trie의 공간 복잡도는 O(max L * K), upper bound
- board의 각 grid에서 dfs를 호출하고, dfs 호출 스택의 최대 깊이는 max L 이므로 O(max L)
- result의 최대 크기는 words의 길이와 같으므로 O(K), upper bound
> O(max L * K) + O(max L) + O(K) ~= O(max L * K)
"""
def solve_trie_dfs(self, board: List[List[str]], words: List[str]) -> List[str]:
MAX_R, MAX_C = len(board), len(board[0])
DIRS = [(0, 1), (1, 0), (0, -1), (-1, 0)]

trie = Trie()
for word in words:
trie.insert(word)

def dfs(curr: Node, r: int, c: int, path: str):
nonlocal result

if not (0 <= r < MAX_R and 0 <= c < MAX_C):
return

if board[r][c] == "#":
return

char = board[r][c]
if char not in curr.children:
return

post = curr.children[char]
if post.data:
result.add(post.data)

board[r][c] = "#"
for dir_r, dir_c in DIRS:
dfs(post, r + dir_r, c + dir_c, path + char)
board[r][c] = char

result = set()
for r in range(MAX_R):
for c in range(MAX_C):
if board[r][c] in trie.root.children:
dfs(trie.root, r, c, "")

return list(result)


class _LeetCodeTestCases(TestCase):
def test_1(self):
board = [["o", "a", "a", "n"], ["e", "t", "a", "e"], ["i", "h", "k", "r"], ["i", "f", "l", "v"]]
words = ["oath","pea","eat","rain"]
output = ["eat","oath"]
self.assertEqual(Solution.findWords(Solution(), board, words), output)

def test_2(self):
board = [["a","b"],["c","d"]]
words = ["abcb"]
output = []
self.assertEqual(Solution.findWords(Solution(), board, words), output)


if __name__ == '__main__':
main()

0 comments on commit fad730e

Please sign in to comment.