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] Week7 Solutions #496

Merged
merged 1 commit into from
Sep 27, 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
80 changes: 80 additions & 0 deletions longest-substring-without-repeating-characters/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from unittest import TestCase, main


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

"""
Runtime: 313 ms (Beats 8.97%)
Time Complexity:
- left가 0에서 len(s)까지 조회, right가 left + 1 부터 len(s)까지 조회하므로 O(n * (n + 1) / 2)
- left가 조회할 때마다, 2항 max 연산하므로 * 2
> O((n * (n + 1) / 2) * 2) ~= O(n ^ 2)

Memory: 16.51 (Beats 81.63%)
Space Complexity: O(n)
> checker가 최대 s의 길이만큼 커질 수 있으므로 O(n), upper bound
"""
def solve_two_pointer(self, s: str) -> int:
if not s:
return 0

max_length = 1
for left in range(len(s)):
if len(s) - left + 1 < max_length:
return max_length

right = left + 1
checker = set(s[left])
while right < len(s):
if s[right] in checker:
break

checker.add(s[right])
right += 1

max_length = max(max_length, len(checker))

return max_length

"""
Runtime: 58 ms (Beats 46.47%)
Time Complexity:
- 중복 검사는 set을 사용하므로 O(1)
- right가 len(s)까지 조회하므로 O(n)
- right가 조회한 뒤 2항 max 연산하는데 O(2)
- left가 최대 right까지 조회하고 right < len(s) 이므로 O(n), upper bound
> O(n) * O(2) + O(n) ~= O(n)

Memory: 16.60 (Beats 41.73%)
Space Complexity: O(n)
> checker가 최대 s의 길이만큼 커질 수 있으므로 O(n), upper bound
"""
def solve_sliding_window(self, s: str) -> int:
max_length = 0
left = right = 0
checker = set()
while left < len(s) and right < len(s):
while right < len(s) and s[right] not in checker:
checker.add(s[right])
right += 1

max_length = max(max_length, len(checker))

while left < len(s) and (right < len(s) and s[right] in checker):
checker.remove(s[left])
left += 1
Comment on lines +58 to +67
Copy link
Contributor

Choose a reason for hiding this comment

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

오, while 문 안에 두 개의 while 문이 대칭을 이루니 뭔가 코드가 아름답게 느껴지네요! 😆

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pseudo code스러운 파이썬의 장점 덕분인 것 같습니다. 감사합니다.


return max_length


class _LeetCodeTestCases(TestCase):
def test_1(self):
s = "pwwkew"
output = 3
self.assertEqual(Solution.lengthOfLongestSubstring(Solution(), s), output)


if __name__ == '__main__':
main()
172 changes: 172 additions & 0 deletions number-of-islands/EGON.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from collections import deque
from typing import Generic, List, Optional, TypeVar
from unittest import TestCase, main


T = TypeVar('T')


class Node(Generic[T]):
def __init__(self, value: T):
self.value = value
self.prev = None
self.post = None


class Deque(Generic[T]):
Copy link
Contributor

Choose a reason for hiding this comment

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

Deque을 직접 구현까지 하시고 무료하셨나 봅니다 ㅋ

Copy link
Contributor Author

Choose a reason for hiding this comment

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

기회있을 때마다 자료구조 구현하기...

def __init__(self):
self.head: Optional[Node[T]] = None
self.tail: Optional[Node[T]] = None
self.size = 0

def is_empty(self):
return self.size == 0

def appendright(self, value: T):
node = Node(value)
if self.is_empty():
self.head = self.tail = node
else:
node.prev = self.tail
if self.tail:
self.tail.post = node
self.tail = node
self.size += 1

def appendleft(self, value: T):
node = Node(value)
if self.is_empty():
self.head = self.tail = node
else:
node.post = self.head
if self.head:
self.head.prev = node
self.head = node
self.size += 1

def popright(self) -> T:
if self.is_empty():
raise IndexError("Deque is empty!")

value = self.tail.value
self.tail = self.tail.prev
if self.tail:
self.tail.post = None
else:
self.head = None
self.size -= 1
return value

def popleft(self) -> T:
if self.is_empty():
raise IndexError("Deque is empty!")

value = self.head.value
self.head = self.head.post
if self.head:
self.head.prev = None
else:
self.tail = None
self.size -= 1
return value


class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
return self.solve_bfs_custom(grid)

"""
Runtime: 243 ms (Beats 60.00%)
Time Complexity: O(MAX_R * MAX_C)
- 2차원 배열 grid를 조회하며 deq에 enqueue하는데 O(MAX_R * MAX_C)
- deq의 원소 하나 당 DIRS 크기만큼 탐색에 O(4), 원소에 해당하는 grid의 값이 "0"인 경우 탐색하지 않으므로 upper bound
- deque의 appendleft, popleft에 O(1)
> O(MAX_R * MAX_C) * O(4) ~= O(MAX_R * MAX_C)

Memory: 18.82 (Beats 83.56%)
Space Complexity: O(MAX_R * MAX_C)
- visited 역할을 grid의 원소의 값을 변경하여 사용하였으므로 무시
- deque의 최대 크기는 MAX_R * MAX_C 이므로 O(MAX_R * MAX_C), upper bound
> O(MAX_R * MAX_C)
"""
def solve_bfs(self, grid: List[List[str]]) -> int:

def is_island(r: int, c: int) -> bool:
nonlocal grid

if grid[r][c] != "1":
return False

deq = deque([(r, c)])
deq.appendleft((r, c))
while deq:
curr_r, curr_c = deq.popleft()
grid[r][c] = "0"
for dir_r, dir_c in DIRS:
post_r, post_c = curr_r + dir_r, curr_c + dir_c
if 0 <= post_r < MAX_R and 0 <= post_c < MAX_C and grid[post_r][post_c] == "1":
grid[post_r][post_c] = "0"
deq.appendleft((post_r, post_c))

return True

MAX_R, MAX_C = len(grid), len(grid[0])
DIRS = ((-1, 0), (1, 0), (0, -1), (0, 1))
count = 0
for r in range(MAX_R):
for c in range(MAX_C):
count += 1 if is_island(r, c) else 0
return count

"""
Runtime: 283 ms (Beats 23.05%)
Time Complexity: O(MAX_R * MAX_C)
> solve_bfs와 동일
Memory: 19.98 (Beats 44.38%)
Space Complexity: O(MAX_R * MAX_C)
> solve_bfs와 동일
"""
def solve_bfs_custom(self, grid: List[List[str]]) -> int:

def is_island(r: int, c: int) -> bool:
nonlocal grid

if grid[r][c] != "1":
return False

deq = Deque()
deq.appendleft((r, c))
while not deq.is_empty():
curr_r, curr_c = deq.popleft()
grid[r][c] = "0"
for dir_r, dir_c in DIRS:
post_r, post_c = curr_r + dir_r, curr_c + dir_c
if 0 <= post_r < MAX_R and 0 <= post_c < MAX_C and grid[post_r][post_c] == "1":
grid[post_r][post_c] = "0"
deq.appendleft((post_r, post_c))

return True

MAX_R, MAX_C = len(grid), len(grid[0])
DIRS = ((-1, 0), (1, 0), (0, -1), (0, 1))
count = 0
for r in range(MAX_R):
for c in range(MAX_C):
count += 1 if is_island(r, c) else 0
return count


class _LeetCodeTestCases(TestCase):
def test_1(self):
grid = [
["1", "1", "0", "0", "0"],
["1", "1", "0", "0", "0"],
["0", "0", "1", "0", "0"],
["0", "0", "0", "1", "1"]
]
output = 3
self.assertEqual(Solution.numIslands(Solution(), grid), output)


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


# Definition for singly-linked list.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next

def description(self) -> List[int]:
desc = []
node = self
while node:
desc.append(node.val)
node = node.next

return desc


class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
return self.solve_recursively(head)

"""
Runtime: 41 ms (Beats 28.37%)
Time Complexity: O(n)
- Linked List의 모든 node를 순회하는데 O(n)
- post 변수 할당, curr.next, prev와 curr의 참조 변경은 O(1)
> O(n) * O(1) ~= O(n)

Memory: 17.66 (Beats 85.13%)
Space Complexity: O(1)
- Linked List의 크기와 무관한 ListNode 타입의 prev, curr, post 변수를 할당하여 사용
> O(1)
"""
def solve_iteratively(self, head: Optional[ListNode]) -> Optional[ListNode]:
prev, curr = None, head
while curr:
post = curr.next
curr.next = prev
prev, curr = curr, post

return prev

"""
Runtime: 38 ms (Beats 50.06%)
Time Complexity: O(n)
- Linked List의 모든 node 만큼 재귀를 호출하므로 O(n)
- post 변수 할당, curr.next의 참조 변경은 O(1)
> O(n) * O(1) ~= O(n)

Memory: 17.78 (Beats 27.88%)
Space Complexity: O(n)
> Linked List의 모든 node 만큼 reverse 함수가 스택에 쌓이므로 O(n)
"""
def solve_recursively(self, head: Optional[ListNode]) -> Optional[ListNode]:
def reverse(prev: Optional[ListNode], curr: Optional[ListNode]) -> Optional[ListNode]:
if not curr:
return prev

post = curr.next
curr.next = prev
return reverse(curr, post)

return reverse(None, head)
Comment on lines +58 to +66
Copy link
Contributor

Choose a reason for hiding this comment

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

두 개의 인자를 받는 재귀 구현 배워갑니다 🙇

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dale님 덕분에 즐겁게 문제 풀고 있습니다. 늘 감사드립니다.





class _LeetCodeTestCases(TestCase):
def test_1(self):
node1 = ListNode(val=1)
node2 = ListNode(val=2)
node3 = ListNode(val=3)
node4 = ListNode(val=4)
node5 = ListNode(val=5)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5

output = [5, 4, 3, 2, 1]
self.assertEqual(Solution.reverseList(Solution(), node1).description(), output)


if __name__ == '__main__':
main()
Loading