From 348e0d468f3a481b9a795c6d2372282ce3ee77b5 Mon Sep 17 00:00:00 2001 From: "changmuk.im" Date: Wed, 25 Sep 2024 04:48:40 +0900 Subject: [PATCH] feat : solve with python --- .../EGON.py | 80 ++++++++ number-of-islands/EGON.py | 172 ++++++++++++++++++ reverse-linked-list/EGON.py | 88 +++++++++ set-matrix-zeroes/EGON.py | 65 +++++++ unique-paths/EGON.py | 38 ++++ 5 files changed, 443 insertions(+) create mode 100644 longest-substring-without-repeating-characters/EGON.py create mode 100644 number-of-islands/EGON.py create mode 100644 reverse-linked-list/EGON.py create mode 100644 set-matrix-zeroes/EGON.py create mode 100644 unique-paths/EGON.py diff --git a/longest-substring-without-repeating-characters/EGON.py b/longest-substring-without-repeating-characters/EGON.py new file mode 100644 index 000000000..1e62ff171 --- /dev/null +++ b/longest-substring-without-repeating-characters/EGON.py @@ -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 + + 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() diff --git a/number-of-islands/EGON.py b/number-of-islands/EGON.py new file mode 100644 index 000000000..91d9c2ac2 --- /dev/null +++ b/number-of-islands/EGON.py @@ -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]): + 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() diff --git a/reverse-linked-list/EGON.py b/reverse-linked-list/EGON.py new file mode 100644 index 000000000..07a6eead1 --- /dev/null +++ b/reverse-linked-list/EGON.py @@ -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) + + + + +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() diff --git a/set-matrix-zeroes/EGON.py b/set-matrix-zeroes/EGON.py new file mode 100644 index 000000000..89749b599 --- /dev/null +++ b/set-matrix-zeroes/EGON.py @@ -0,0 +1,65 @@ +from typing import List +from unittest import TestCase, main +from math import isnan + + +class Solution: + def setZeroes(self, matrix: List[List[int]]) -> None: + return self.solve(matrix) + + """ + Runtime: 107 ms (Beats 31.81%) + Time Complexity: + - matrix의 원소에 R_MARKER, C_MARKER를 더하는데 range(m), range(n) 조회에 O(m * n) + - MARKER가 아닌 matrix의 원소를 0으로 바꾸는데 O(m * n) + - MARKER인 matrix의 원소를 0으로 바꾸는데 O(m + n) + > O(m * n) + O(m * n) + O(m + n) = O(2 * m * n) + O(m + n) ~= O(m * n) + O(m + n) + Memory: 17.49 (Beats 58.33%) + Space Complexity: O(1) + > matrix의 원소의 값만 변경한 in-place 풀이이므로 O(1) + """ + def solve(self, matrix: List[List[int]]) -> None: + MAX_R, MAX_C = len(matrix), len(matrix[0]) + R_MARKER, C_MARKER, CROSS_MARKER = float('inf'), float('-inf'), float('nan') + + for r in range(MAX_R): + for c in range(MAX_C): + if matrix[r][c] == 0: + matrix[r][0] += R_MARKER + matrix[0][c] += C_MARKER + + for r in range(MAX_R): + for c in range(MAX_C): + if isnan(matrix[r][c]) or (matrix[r][c] in (R_MARKER, C_MARKER)): + continue + + if isnan(matrix[r][0]) or matrix[r][0] == R_MARKER: + matrix[r][c] = 0 + + if isnan(matrix[0][c]) or matrix[0][c] == C_MARKER: + matrix[r][c] = 0 + + for r in range(MAX_R): + if isnan(matrix[r][0]) or matrix[r][0] == R_MARKER: + matrix[r][0] = 0 + for c in range(MAX_C): + if isnan(matrix[0][c]) or matrix[0][c] == C_MARKER: + matrix[0][c] = 0 + + +class _LeetCodeTestCases(TestCase): + def test_1(self): + matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]] + output = [[0,0,0,0],[0,4,5,0],[0,3,1,0]] + Solution.setZeroes(Solution(), matrix) + self.assertEqual(matrix, output) + + def test_2(self): + matrix = [[1,2,3,4],[5,0,7,8],[0,10,11,12],[13,14,15,0]] + output = [[0,0,3,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]] + Solution.setZeroes(Solution(), matrix) + self.assertEqual(matrix, output) + + +if __name__ == '__main__': + main() diff --git a/unique-paths/EGON.py b/unique-paths/EGON.py new file mode 100644 index 000000000..edcfe3405 --- /dev/null +++ b/unique-paths/EGON.py @@ -0,0 +1,38 @@ +from unittest import TestCase, main + + +class Solution: + def uniquePaths(self, m: int, n: int) -> int: + return self.solve_dp(m, n) + + """ + Runtime: 33 ms (Beats 73.78%) + Time Complexity: O(m * n) + - m * n 크기의 2차원 배열 dp 생성에 O(m * n) + - range(1, m) * range(1, n) 중첩 조회에 O((m - 1) * (n - 1)) + - result로 dp[-1][-1] 조회에 O(1) + > O(m * n) + O((m - 1) * (n - 1)) + O(1) ~= O(m * n) + O(m * n) ~= O(m * n) + + Memory: 16.44 (Beats 77.15%) + Space Complexity: O(m * n) + > m * n 크기의 2차원 배열 dp 사용에 O(m * n) + """ + def solve_dp(self, m: int, n: int) -> int: + dp = [[1 if r == 0 or c == 0 else 0 for c in range(n)] for r in range(m)] + for r in range(1, m): + for c in range(1, n): + dp[r][c] = dp[r - 1][c] + dp[r][c - 1] + + return dp[-1][-1] + + +class _LeetCodeTestCases(TestCase): + def test_1(self): + m = 3 + n = 7 + output = 28 + self.assertEqual(Solution.uniquePaths(Solution(), m, n), output) + + +if __name__ == '__main__': + main()