diff --git a/find-minimum-in-rotated-sorted-array/EGON.py b/find-minimum-in-rotated-sorted-array/EGON.py new file mode 100644 index 000000000..9efc78205 --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/EGON.py @@ -0,0 +1,43 @@ +from typing import List +from unittest import TestCase, main + + +class Solution: + def findMin(self, nums: List[int]) -> int: + return self.solve_binary_search(nums) + + """ + Runtime: 32 ms (Beats 97.56%) + Time Complexity: O(log n) + - 크기가 n인 배열에 대한 이분탐색에 O(log n) + - while 조건문 판단에 O(2), and 연산이므로 단축 평가에 의해 upper bound + - 엣지 케이스 처리를 위한 마지막 lo, hi 2개 항에 대한 min연산에 O(2) + > O(log n) * O(2) + O(2) ~= O(log n) + + Memory: 16.82 (Beats 50.00%) + Space Complexity: O(1) + > 이분탐색에 필요한 정수형 변수 lo, hi, mid 3개만 사용했으므로 n과 상관없이 O(1) + """ + def solve_binary_search(self, nums: List[int]) -> int: + lo, hi = 0, len(nums) - 1 + while lo < hi and nums[hi] < nums[lo]: + mid = (lo + hi) // 2 + if nums[lo] < nums[mid]: + lo = mid + elif nums[mid] < nums[hi]: + hi = mid + else: + break + + return min(nums[lo], nums[hi]) + + +class _LeetCodeTestCases(TestCase): + def test_1(self): + nums = [2, 1] + output = 1 + self.assertEqual(Solution().findMin(nums), output) + + +if __name__ == '__main__': + main() diff --git a/linked-list-cycle/EGON.py b/linked-list-cycle/EGON.py new file mode 100644 index 000000000..724f0131a --- /dev/null +++ b/linked-list-cycle/EGON.py @@ -0,0 +1,47 @@ +from typing import Optional +from unittest import TestCase, main + + +# Definition for singly-linked list. +class ListNode: + def __init__(self, x): + self.val = x + self.next = None + + +class Solution: + def hasCycle(self, head: Optional[ListNode]) -> bool: + return self.solve(head) + + """ + Runtime: 37 ms (Beats 93.02%) + Time Complexity: O(n) + > head부터 next가 있는 동안 선형적으로 조회하므로 O(n) + + Memory: 18.62 (Beats 98.22%) + Space Complexity: O(1) + > head를 제외하고 아무 변수도 사용하지 않았으므로 O(1) + """ + def solve(self, head: Optional[ListNode]) -> bool: + if not head: + return False + + while head.next: + if head.next and head.next.val is None: + return True + + head.val = None + head = head.next + + return False + + +class _LeetCodeTestCases(TestCase): + def test_1(self): + head = None + output = False + self.assertEqual(Solution().hasCycle(head), output) + + +if __name__ == '__main__': + main() diff --git a/maximum-product-subarray/EGON.py b/maximum-product-subarray/EGON.py new file mode 100644 index 000000000..02f3fc7e4 --- /dev/null +++ b/maximum-product-subarray/EGON.py @@ -0,0 +1,67 @@ +from typing import List +from unittest import TestCase, main + + +class Solution: + def maxProduct(self, nums: List[int]) -> int: + return self.solveWithDP(nums) + + """ + Runtime: 71 ms (Beats 61.13%) + Time Complexity: O(n) + - dp 배열 초기화를 위한 nums.copy()에 O(n) + - range(1, L) 조회하며 조건에 따라 연산에 O(n - 1) + - range(L) 조회하며 max 계산에 O(n) + > O(n) + O(n - 1) + O(n) ~= O(n) + + Memory: 17.75 MB (Beats 11.09%) + Space Complexity: O(n) + - 크기가 n인 배열 2개 사용했으므로 2 * O(n) + > O(2n) ~= O(n) + """ + def solveWithDP(self, nums: List[int]) -> int: + L = len(nums) + forward_product, backward_product = nums.copy(), nums.copy() + for i in range(1, L): + if forward_product[i - 1] != 0: + forward_product[i] *= forward_product[i - 1] + + if backward_product[L - i] != 0: + backward_product[L - i - 1] *= backward_product[L - i] + + result = nums[0] + for i in range(L): + result = max(result, forward_product[i], backward_product[i]) + + return result + + +class _LeetCodeTestCases(TestCase): + def test_1(self): + nums = [2,3,-2,4] + output = 6 + self.assertEqual(Solution.maxProduct(Solution(), nums), output) + + def test_2(self): + nums = [-2,0,-1] + output = 0 + self.assertEqual(Solution.maxProduct(Solution(), nums), output) + + def test_3(self): + nums = [-2] + output = -2 + self.assertEqual(Solution.maxProduct(Solution(), nums), output) + + def test_4(self): + nums = [0,-3,-2,-3,-2,2,-3,0,1,-1] + output = 72 + self.assertEqual(Solution.maxProduct(Solution(), nums), output) + + def test_5(self): + nums = [7, -2, -4] + output = 56 + self.assertEqual(Solution.maxProduct(Solution(), nums), output) + + +if __name__ == '__main__': + main() diff --git a/maximum-subarray/EGON.py b/maximum-subarray/EGON.py index 02f3fc7e4..ab670028f 100644 --- a/maximum-subarray/EGON.py +++ b/maximum-subarray/EGON.py @@ -3,64 +3,86 @@ class Solution: - def maxProduct(self, nums: List[int]) -> int: - return self.solveWithDP(nums) + def maxSubArray(self, nums: List[int]) -> int: + return self.solve_divide_and_conquer(nums) """ - Runtime: 71 ms (Beats 61.13%) + Runtime: 548 ms (Beats 38.42%) Time Complexity: O(n) - - dp 배열 초기화를 위한 nums.copy()에 O(n) - - range(1, L) 조회하며 조건에 따라 연산에 O(n - 1) - - range(L) 조회하며 max 계산에 O(n) - > O(n) + O(n - 1) + O(n) ~= O(n) + - nums를 조회하는데 O(n) + - max_sum을 갱신하는데 2개 항에 대한 max연산에 O(2) + - max_subarray_sum을 갱신하는데 2개 항에 대한 max 연산에 O(2) + > O(n) * (O(2) + O(2)) = O(4 * n) ~= O(n) - Memory: 17.75 MB (Beats 11.09%) + Memory: 30.96 MB (Beats 74.82%) + Space Complexity: O(1) + > 정수형 변수, 실수형 변수 하나 씩만 사용했으므로 O(1) + """ + def solve_kadane(self, nums: List[int]) -> int: + max_subarray_sum, result = 0, float('-inf') + for num in nums: + max_subarray_sum = max(num, max_subarray_sum + num) + result = max(max_subarray_sum, result) + return result + + """ + Runtime: 732 ms (Beats 5.04%) + Time Complexity: O(n * log n) + - max_prefix_sum에서 deepcopy에 O(n), 계산에 O(n) + - max_suffix_sum에서 deepcopy에 O(n), 계산에 O(n) + - divide_and_sum에서 재귀 호출 depth가 log n, 호출 결과의 최대 갯수는 n이므로, 일반적인 divide and conquer의 시간복잡도와 동일한 O(n * log n) + > 2 * O(n) + 2 * O(n) + O(n * log n) ~= O(n * log n) + + Memory: 68.75 MB (Beats 20.29%) Space Complexity: O(n) - - 크기가 n인 배열 2개 사용했으므로 2 * O(n) - > O(2n) ~= O(n) + - max_prefix_sum에서 O(n) + - max_suffix_sum에서 O(n) + > O(n) + O(n) = 2 * O(n) ~= O(n) """ - def solveWithDP(self, nums: List[int]) -> int: - L = len(nums) - forward_product, backward_product = nums.copy(), nums.copy() - for i in range(1, L): - if forward_product[i - 1] != 0: - forward_product[i] *= forward_product[i - 1] + def solve_divide_and_conquer(self, nums: List[int]) -> int: + max_prefix_sum = nums[::] + for i in range(1, len(nums)): + max_prefix_sum[i] = max(max_prefix_sum[i], max_prefix_sum[i - 1] + nums[i]) - if backward_product[L - i] != 0: - backward_product[L - i - 1] *= backward_product[L - i] + max_suffix_sum = nums[::] + for i in range(len(nums) - 2, -1, -1): + max_suffix_sum[i] = max(max_suffix_sum[i], max_suffix_sum[i + 1] + nums[i]) - result = nums[0] - for i in range(L): - result = max(result, forward_product[i], backward_product[i]) + def divide_and_sum(nums: List[int], left: int, right: int) -> int: + if left == right: + return nums[left] - return result + mid = (left + right) // 2 + + return max( + divide_and_sum(nums, left, mid), + max_prefix_sum[mid] + max_suffix_sum[mid + 1], + divide_and_sum(nums, mid + 1, right) + ) + + return divide_and_sum(nums, 0, len(nums) - 1) class _LeetCodeTestCases(TestCase): def test_1(self): - nums = [2,3,-2,4] + nums = [-2,1,-3,4,-1,2,1,-5,4] output = 6 - self.assertEqual(Solution.maxProduct(Solution(), nums), output) + self.assertEqual(Solution.maxSubArray(Solution(), nums), output) def test_2(self): - nums = [-2,0,-1] - output = 0 - self.assertEqual(Solution.maxProduct(Solution(), nums), output) + nums = [1] + output = 1 + self.assertEqual(Solution.maxSubArray(Solution(), nums), output) def test_3(self): - nums = [-2] - output = -2 - self.assertEqual(Solution.maxProduct(Solution(), nums), output) + nums = [5,4,-1,7,8] + output = 23 + self.assertEqual(Solution.maxSubArray(Solution(), nums), output) def test_4(self): - nums = [0,-3,-2,-3,-2,2,-3,0,1,-1] - output = 72 - self.assertEqual(Solution.maxProduct(Solution(), nums), output) - - def test_5(self): - nums = [7, -2, -4] - output = 56 - self.assertEqual(Solution.maxProduct(Solution(), nums), output) + nums = [-4, -3, -2, -1] + output = -1 + self.assertEqual(Solution.maxSubArray(Solution(), nums), output) if __name__ == '__main__': diff --git a/minimum-window-substring/EGON.py b/minimum-window-substring/EGON.py new file mode 100644 index 000000000..c56dc057d --- /dev/null +++ b/minimum-window-substring/EGON.py @@ -0,0 +1,67 @@ +from collections import Counter +from typing import List +from unittest import TestCase, main + + +class Solution: + def minWindow(self, s: str, t: str) -> str: + return self.solve_two_pointer(s, t) + + """ + Runtime: 129 ms (Beats 50.44%) + Time Complexity: O(S) + - 문자열 s를 enumerate로 순회하는데 O(S) + - 순회 후 left를 갱신하는 while문에서 left가 0부터 n까지 단조증가하므로 총 조회는 O(S) + > O(S) + O(S) ~= O(S) + + Memory: 17.32 MB (Beats 32.52%) + Space Complexity: O(S) + - counter 변수의 초기 크기는 O(T) + - 반복문을 조회하며 counter 갱신, 최악의 경우 s의 모든 문자가 다르고 s == t인 경우 이므로 O(S), upper bound + > O(S) + """ + def solve_two_pointer(self, s: str, t: str) -> str: + counter = Counter(t) + missing = len(t) + left = start = end = 0 + for right, char in enumerate(s, start=1): + missing -= counter[char] > 0 + counter[char] -= 1 + + if missing == 0: + while left < right and counter[s[left]] < 0: + counter[s[left]] += 1 + left += 1 + + if not end or right - left <= end - start: + start, end = left, right + + counter[s[left]] += 1 + missing += 1 + left += 1 + + return s[start:end] + + +class _LeetCodeTestCases(TestCase): + def test_1(self): + s = "ADOBECODEBANC" + t = "ABC" + output = "BANC" + self.assertEqual(Solution.minWindow(Solution(), s, t), output) + + def test_2(self): + s = "a" + t = "a" + output = "a" + self.assertEqual(Solution.minWindow(Solution(), s, t), output) + + def test_3(self): + s = "a" + t = "aa" + output = "" + self.assertEqual(Solution.minWindow(Solution(), s, t), output) + + +if __name__ == '__main__': + main() diff --git a/pacific-atlantic-water-flow/EGON.py b/pacific-atlantic-water-flow/EGON.py new file mode 100644 index 000000000..d84bad0f6 --- /dev/null +++ b/pacific-atlantic-water-flow/EGON.py @@ -0,0 +1,88 @@ +from typing import List +from unittest import TestCase, main + + +class Solution: + def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]: + return self.solve_dfs(heights) + + """ + Runtime: 416 ms (Beats 11.61%) + Time Complexity: O((r * c) ^ 2) + - memo_pacific 생성에 O(r * c) + - memo_atlantic 생성에 O(r * c) + - heights의 모든 grid에 대해 조회하는데 O(r * c) + - 조회하면서 시행하는 dfs 2회 조회하는데 2 * O(r * c), upper bound + - heights의 모든 grid에 대해 조회하며 result 생성에 O(r * c) + > O(r * c) + O(r * c) + O(r * c) * (2 * O(r * c)) + O(r * c) = 3 * O(r * c) + 2 * O((r * c) ^ 2) ~= O((r * c) ^ 2) + + Memory: 18.30 (Beats 37.99%) + Space Complexity: O(1) + - memo_pacific 생성에 O(r * c) + - memo_atlantic 생성에 O(r * c) + - dfs에서 stack의 최대 크기는 O(r * c), upper bound + - dfs를 2회 사용하는데 * 2 + > O(r * c) + O(r * c) + 2 * O(r * c) = 4 * O(r * c) ~= O(r * c) + """ + def solve_dfs(self, heights: List[List[int]]) -> List[List[int]]: + MAX_R, MAX_C = len(heights), len(heights[0]) + DIRS = ((-1, 0), (0, 1), (1, 0), (0, -1)) + + def dfs(r: int, c: int, memo: List[List[any]]): + stack = [[r, c]] + while stack: + curr_r, curr_c = stack.pop() + for dir_r, dir_c in DIRS: + if not (0 <= curr_r + dir_r < MAX_R and 0 <= curr_c + dir_c < MAX_C): + continue + + if heights[curr_r][curr_c] > heights[curr_r + dir_r][curr_c + dir_c]: + continue + + if memo[curr_r][curr_c] is not None and memo[curr_r + dir_r][curr_c + dir_c] is None: + memo[curr_r + dir_r][curr_c + dir_c] = memo[curr_r][curr_c] + stack.append([curr_r + dir_r, curr_c + dir_c]) + + memo_pacific = [[True if r == 0 or c == 0 else None for r in range(MAX_C)] for c in range(MAX_R)] + memo_atlantic = [[True if r == MAX_R - 1 or c == MAX_C - 1 else None for c in range(MAX_C)] for r in range(MAX_R)] + result = [] + for r in range(MAX_R): + for c in range(MAX_C): + r_pacific, c_pacific = r, c + dfs(r_pacific, c_pacific, memo_pacific) + + r_atlantic, c_atlantic = MAX_R - r_pacific - 1, MAX_C - c_pacific - 1 + dfs(r_atlantic, c_atlantic, memo_atlantic) + + for r in range(MAX_R): + for c in range(MAX_C): + if memo_pacific[r][c] and memo_atlantic[r][c]: + result.append([r, c]) + + return result + + +class _LeetCodeTestCases(TestCase): + def test_1(self): + heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]] + output = [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]] + self.assertEqual(Solution().pacificAtlantic(heights), output) + + def test_2(self): + heights = [[1]] + output = [[0,0]] + self.assertEqual(Solution().pacificAtlantic(heights), output) + + def test_3(self): + heights = [[1,2,3],[8,9,4],[7,6,5]] + output = [[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]] + self.assertEqual(Solution().pacificAtlantic(heights), output) + + def test_4(self): + heights = [[1,1],[1,1],[1,1]] + output = [[0, 0], [0, 1], [1, 0], [1, 1], [2, 0], [2, 1]] + self.assertEqual(Solution().pacificAtlantic(heights), output) + + +if __name__ == '__main__': + main()