Skip to content

Commit

Permalink
Merge pull request #525 from lymchgmk/feat/week9
Browse files Browse the repository at this point in the history
[EGON]] Week9 Solutions
  • Loading branch information
EgonD3V authored Oct 12, 2024
2 parents 0b457eb + b15bc49 commit 0ef1547
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 38 deletions.
43 changes: 43 additions & 0 deletions find-minimum-in-rotated-sorted-array/EGON.py
Original file line number Diff line number Diff line change
@@ -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()
47 changes: 47 additions & 0 deletions linked-list-cycle/EGON.py
Original file line number Diff line number Diff line change
@@ -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()
67 changes: 67 additions & 0 deletions maximum-product-subarray/EGON.py
Original file line number Diff line number Diff line change
@@ -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()
98 changes: 60 additions & 38 deletions maximum-subarray/EGON.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__':
Expand Down
67 changes: 67 additions & 0 deletions minimum-window-substring/EGON.py
Original file line number Diff line number Diff line change
@@ -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()
Loading

0 comments on commit 0ef1547

Please sign in to comment.