diff --git a/coin-change/EGON.py b/coin-change/EGON.py index 9b08d239e..833f8d977 100644 --- a/coin-change/EGON.py +++ b/coin-change/EGON.py @@ -1,14 +1,25 @@ from typing import List from unittest import TestCase, main -from collections import defaultdict class Solution: def coinChange(self, coins: List[int], amount: int) -> int: - return self.solve_with_dp(coins, amount) + return self.solveWithDP(coins, amount) # Unbounded Knapsack Problem - def solve_with_dp(self, coins: List[int], amount: int) -> int: + """ + Runtime: 801 ms (Beats 48.54%) + Time Complexity: O(n) + - coins 길이를 c, amount의 크기를 a라고 하면 + - coins를 정렬하는데 O(log c) + - dp 배열 조회에 O((n + 1) * c) + > c의 최대 크기는 12라서 무시가능하므로 O((n + 1) * c) ~= O(n * c) ~= O(n) + + Memory: 16.94 MB (Beats 50.74%) + Space Complexity: O(n) + > 크기가 n + 1인 dp를 선언하여 사용했으므로 O(n + 1) ~= O(n) + """ + def solveWithDP(self, coins: List[int], amount: int) -> int: if amount == 0: return 0 @@ -16,23 +27,22 @@ def solve_with_dp(self, coins: List[int], amount: int) -> int: if amount < coins[0]: return -1 + + dp = [float('inf')] * (amount + 1) - dp = [[0] * (amount + 1) for _ in range(len(coins) + 1)] - for curr_r in range(1, len(coins) + 1): - coin_index = curr_r - 1 - curr_coin = coins[coin_index] - if amount < curr_coin: - continue + for coin in coins: + if coin <= amount: + dp[coin] = 1 - dp[curr_r][curr_coin] += 1 - for curr_amount in range(curr_coin + 1, amount + 1): - for coin in coins: - if 0 < dp[curr_r][curr_amount - coin]: - dp[curr_r][curr_amount] = max(dp[curr_r - 1][curr_amount], dp[curr_r][curr_amount - coin] + 1) - else: - dp[curr_r][curr_amount] = dp[curr_r - 1][curr_amount] + for curr_amount in range(amount + 1): + for coin in coins: + if 0 <= curr_amount - coin: + dp[curr_amount] = min( + dp[curr_amount], + dp[curr_amount - coin] + 1 + ) - return dp[-1][-1] if 0 < dp[-1][-1] else -1 + return dp[-1] if dp[-1] != float('inf') else -1 class _LeetCodeTestCases(TestCase): @@ -57,7 +67,7 @@ def test_3(self): def test_4(self): coins = [1, 2147483647] amount = 2 - output = -1 + output = 2 self.assertEqual(Solution.coinChange(Solution(), coins, amount), output) diff --git a/combination-sum/EGON.py b/combination-sum/EGON.py index 13653d49e..ffaa6cbd3 100644 --- a/combination-sum/EGON.py +++ b/combination-sum/EGON.py @@ -5,16 +5,23 @@ class Solution: def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: - return self.solve_with_dfs(candidates, target) + return self.solveWithBackTracking(candidates, target) """ Runtime: 2039 ms (Beats 5.01%) - Time Complexity: ? - + Time Complexity: O(c * c * log c) + - 처음 stack의 크기는 c에 비례 O(c) + - 중복 제거에 사용하는 변수인 curr_visited_checker 생성에 O(c' log c') + - stack의 내부 로직에서 c에 비례한 for문을 순회하는데 O(c) + > O(c) * O(c' log c') + O(c) * O(c) ~= O(c * c * log c) + Memory: 16.81 MB (Beats 11.09%) - Space Complexity: ? + Space Complexity: O(c * c) + - curr_combination의 크기가 c에 비례 + - stack의 크기는 curr_combination의 크기와 c에 비례 + > O(c * c) """ - def solve_with_dfs(self, candidates: List[int], target: int) -> List[List[int]]: + def solveWithDFS(self, candidates: List[int], target: int) -> List[List[int]]: result = [] stack = [] visited = defaultdict(bool) @@ -38,6 +45,38 @@ def solve_with_dfs(self, candidates: List[int], target: int) -> List[List[int]]: return result + """ + Runtime: 58 ms (Beats 32.30%) + Time Complexity: O(c * c) + - candidates 정렬에 O(log c) + - 첫 depte에서 dfs 함수 호출에 O(c) + - 그 후 candidates의 길이에 비례해서 재귀적으로 dfs를 호출하는데 O(c) + - lower_bound_idx에 따라 range가 감소하기는 하나 일단은 비례 O(c') + > O(log c) + O(c * c') ~= O(c * c), 단 c' <= c 이므로 이 복잡도는 upper bound + Memory: 16.59 MB (Beats 75.00%) + Space Complexity: O(c) + - result를 제외하고 모두 nonlocal 변수를 call by reference로 참조 + - dfs 함수 호출마다 메모리가 증가하는데, 호출횟수는 candidates의 길이에 비례 O(c) + - lower_bound_idx에 따라 range가 감소하기는 하나 일단은 비례 + > O(c), 단 이 복잡도는 upper bound + """ + def solveWithBackTracking(self, candidates: List[int], target: int) -> List[List[int]]: + def dfs(stack: List[int], sum: int, lower_bound_idx: int): + nonlocal result, candidates, target + + if target < sum: + return + elif sum < target: + for idx in range(lower_bound_idx, len(candidates)): + dfs(stack + [candidates[idx]], sum + candidates[idx], idx) + else: # target == sum + result.append(stack) + return + + result = [] + candidates.sort() + dfs([], 0, 0) + return result class _LeetCodeTestCases(TestCase): def test_1(self): diff --git a/two-sum/EGON.py b/two-sum/EGON.py index a44042d47..e69a83439 100644 --- a/two-sum/EGON.py +++ b/two-sum/EGON.py @@ -10,10 +10,11 @@ def twoSum(self, nums: List[int], target: int) -> List[int]: """ Runtime: 3762 ms (Beats 5.00%) Time Complexity: O(n ** 2) - - 크기가 n인 nums 배열을 2중으로 조회하므로 O(n ** 2) + > 크기가 n인 nums 배열을 2중으로 조회하므로 O(n ** 2) Memory: 17.42 MB (Beats 61.58%) - Space Complexity: ? + Space Complexity: O(1) + > 딱히 저장하는 변수 없음 (반환하는 list 제외) """ def solveWithBruteForce(self, nums: List[int], target: int) -> List[int]: for i in range(len(nums)):