Skip to content
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

[EGON] Week8 Solutions #507

Merged
merged 1 commit into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions clone-graph/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import Optional, TypeVar
from unittest import TestCase, main


# 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 []


class Solution:
def cloneGraph(self, node: Optional[Node]) -> Optional[Node]:
return self.solve(node)

"""
Runtime: 42 ms (Beats 45.48%)
Time Complexity: O(n)

Memory: 17.04 (Beats 8.15%)
Space Complexity: O(n), upper bound
"""
def solve(self, node: Optional[Node]) -> Optional[Node]:
clone_dict = {}
stack = [node]
visited = set()
while stack:
curr_node = stack.pop()
visited.add(curr_node.val)
if curr_node.val not in clone_dict:
clone_dict[curr_node.val] = Node(val=curr_node.val)

for neighbor in curr_node.neighbors:
if neighbor.val not in clone_dict:
clone_dict[neighbor.val] = Node(val=neighbor.val)

if neighbor.val not in visited:
clone_dict[curr_node.val].neighbors.append(clone_dict[neighbor.val])
clone_dict[neighbor.val].neighbors.append(clone_dict[curr_node.val])
stack.append(neighbor)

return clone_dict[node.val]


class _LeetCodeTestCases(TestCase):
def test_1(self):
node_dict = {1: Node(1), 2: Node(2), 3: Node(3), 4: Node(4)}
node_dict[1].neighbors = [node_dict[2], node_dict[4]]
node_dict[2].neighbors = [node_dict[1], node_dict[3]]
node_dict[3].neighbors = [node_dict[2], node_dict[4]]
node_dict[4].neighbors = [node_dict[1], node_dict[3]]

Solution.cloneGraph(Solution(), node_dict[1])
self.assertTrue("RUN")


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


class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
return self.solve_dp(text1, text2)

"""
Runtime: 441 ms (Beats 85.60%)
Time Complexity: O(m * n)
- text1, text2의 길이를 각각 m, n이라 하면 이중 for문 조회에 O(m * n)
- i. dp 배열 값 갱신에서 기존값 +1에 O(1)
- ii. dp 배열 값 갱신에서 2개 항에 대한 max 연산에 O(2), upper bound
> O(m * n) * O(2) ~= O(m * n)

Memory: 41.81 (Beats 55.93%)
Space Complexity: O(m * n)
> row의 길이가 n이가 col의 길이가 m인 2차원 배열 dp 사용에 O(m * n)
"""
def solve_dp(self, text1: str, text2: str) -> int:
dp = [[0] * (len(text2) + 1) for _ in range(len(text1) + 1)]
for idx1 in range(len(text1)):
for idx2 in range(len(text2)):
if text1[idx1] == text2[idx2]:
dp[idx1 + 1][idx2 + 1] = dp[idx1][idx2] + 1
else:
dp[idx1 + 1][idx2 + 1] = max(dp[idx1 + 1][idx2], dp[idx1][idx2 + 1])

return dp[-1][-1]


class _LeetCodeTestCases(TestCase):
def test_1(self):
text1 = "abcde"
text2 = "ace"
output = 3
self.assertEqual(
Solution().longestCommonSubsequence(text1, text2),
output
)


if __name__ == '__main__':
main()
48 changes: 48 additions & 0 deletions longest-repeating-character-replacement/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from unittest import TestCase, main
from collections import defaultdict


class Solution:
def characterReplacement(self, s: str, k: int) -> int:
return self.solve_sliding_window(s, k)

"""
Runtime: 63 ms (Beats 98.40%)
Time Complexity: O(n)
- s에 대해 조회하는데 O(n)
- dict인 counter를 사용해서 특정 문자의 빈도수 체크, 분기처리, most_common_char 갱신에 O(1)
- 현재 최장 길이 계산에 int 계산만 사용하므로 O(1)
> O(n) * O(1) ~= O(n)

Memory: 16.54 (Beats 76.70%)
Space Complexity: O(n), upper bound
- left, right 같은 int 인덱스 변수 할당에 O(1)
- most_common_char같은 str 변수 할당에 O(1)
- dict인 counter의 최대 크기는 s의 모든 문자가 다르고 k의 크기가 len(s)인 경우이므로 O(n), upper bound
> O(n), upper bound
"""
def solve_sliding_window(self, s: str, k: int) -> int:
left = right = 0
counter, most_common_char = defaultdict(int), ""
for right in range(len(s)):
counter[s[right]] += 1
if counter[most_common_char] < counter[s[right]]:
most_common_char = s[right]

if (right - left + 1) - counter[most_common_char] > k:
counter[s[left]] -= 1
left += 1

return right - left + 1


class _LeetCodeTestCases(TestCase):
def test_1(self):
s = "AABBCC"
k = 2
output = 4
self.assertEqual(Solution.characterReplacement(Solution(), s, k), output)


if __name__ == '__main__':
main()
79 changes: 79 additions & 0 deletions merge-two-sorted-lists/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from typing import Optional
from unittest import TestCase, main


class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next


class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
return self.solve(list1, list2)

"""
Runtime: 36 ms (Beats 71.91%)
Time Complexity: O(m + n)
> list1의 길이를 m, list2의 길이를 n이라 할 때, list1과 list2 모두 끝까지 조회하므로 O(n + m)

Memory: 16.62 (Beats 37.59%)
Space Complexity: O(m + n)
> result의 길이는 list1의 길이와 list2의 길이의 합과 같으므로 O(m + n)
"""
def solve(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
result = ListNode()
node = result
while list1 or list2:
if list1 and list2:
if list1.val < list2.val:
node.next = list1
node = node.next
list1 = list1.next
else:
node.next = list2
node = node.next
list2 = list2.next

elif list1 and not list2:
node.next = list1
node = node.next
list1 = list1.next

elif not list1 and list2:
node.next = list2
node = node.next
list2 = list2.next

else:
break

return result.next
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 하면 첫 아이템을 만드는 로직을 분리하지 않고 한 번에 처리할 수 있었네요..! 어떻게 할까 고민하다가 생각 못하고 넘어갔는데 좋은 아이디어 감사합니다!



class _LeetCodeTestCases(TestCase):
def test_1(self):
list1 = ListNode(
val=1,
next=ListNode(
val=2,
next=ListNode(
val=4
)
)
)
list2 = ListNode(
val=1,
next=ListNode(
val=3,
next=ListNode(
val=4
)
)
)
output = [1,1,2,3,4,4]
self.assertEqual(Solution.mergeTwoLists(Solution(), list1, list2), output)


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


class Solution:
def getSum(self, a: int, b: int) -> int:
return self.solve_bit(a, b)

"""
Runtime: 26 ms (Beats 94.94%)
Time Complexity: O(log max(a, b)) ~= O(1)
- a와 b를 and 연산 후 왼쪽으로 shift한 값을 b에 할당하는데 모두 비트연산이므로 O(1)
- b의 값이 왼쪽 shift에 의해 감소하므로 log(b)
- 단 b의 값은 a & b에 의해 결정되므로 log max(a, b)
- 전제 조건에서 a, b는 모두 절대값 1000 이하의 값이므로, 최대 10회의 shift 연산만 발생하므로, O(10) ~= O(1)
> O(log max(a, b)) < O(10) ~= O(1)

Memory: 16.52 (Beats 18.98%)
Space Complexity: O(1)
> input에 무관하게 정수형 변수들만 사용하므로 O(1)
"""
def solve_bit(self, a: int, b: int) -> int:
COMPLEMENT_MASK = 0xFFF

while b != 0:
carry = a & b
a = (a ^ b) & COMPLEMENT_MASK
b = (carry << 1) & COMPLEMENT_MASK

return a if a <= 0x7FF else ~(a ^ COMPLEMENT_MASK)


class _LeetCodeTestCases(TestCase):
def test_1(self):
a = -1
b = 1
output = 0
self.assertEqual(Solution().getSum(a, b), output)


if __name__ == '__main__':
main()