diff --git a/3sum/liza0525.py b/3sum/liza0525.py index 699244d5d..3be236c62 100644 --- a/3sum/liza0525.py +++ b/3sum/liza0525.py @@ -36,3 +36,39 @@ def threeSum(self, nums: List[int]) -> List[List[int]]: results.add((nums[i], nums[left], nums[right])) left, right = left + 1, right - 1 return list(results) + + +# 7기 풀이 +# 시간 복잡도: O(n^2) +# - 배열 정렬: O(n log n) +# - for문과 while문을 이용한 이중 loop문으로 탐색: O(n^2) +# - 전체 시간 복잡도는 O(n^2) +# 공간 복잡도: O(1) +# - 결과 저장 공간은 output이므로 제외 (변수 이름: results) +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + nums.sort() # 정렬을 한 번 한 후 + results = set() + + # for 문과 투포인터를 이용해 문제를 푼다. + for i in range(len(nums) - 2): + # i번째와 i - 1 번째 값이 같은 경우에는 이미 이전 loop에서 계산했기 때문에 다음 루프로 넘긴다 + if i > 0 and nums[i] == nums[i - 1]: + continue + + # 포인터 지정 + left, right = i + 1, len(nums) - 1 + while left < right: + result = nums[i] + nums[left] + nums[right] + if result < 0: + # 합이 0보다 작다면 요소의 값을 올려야 하기 때문에 left를 올린다. (nums는 정렬이 된 상태) + left += 1 + elif result > 0: + # 합이 0보다 크다면 요소의 값을 줄여야 하기 때문에 right를 내린다. (nums는 정렬이 된 상태) + right -= 1 + else: + # 합이 0이면 결과 저장 후, 포인터를 조정하여 다음 triplet 세트를 찾는다. + results.add((nums[i], nums[left], nums[right])) + left, right = left + 1, right - 1 + + return list(results) diff --git a/climbing-stairs/liza0525.py b/climbing-stairs/liza0525.py index 2086c75b7..05fc51d97 100644 --- a/climbing-stairs/liza0525.py +++ b/climbing-stairs/liza0525.py @@ -15,3 +15,34 @@ def fibonacci(step): fibonacci(3) return memo[n] + + +# 7기 풀이 +# 시간 복잡도: O(n) +# - memoization을 이용해 결과를 저장을 하면, 계산은 0 ~ n까지 한 번씩만 계산 +# - 즉, 계단의 개수 n이 최대 계산 횟수이므로 전체 연산은 O(n) +# 공간 복잡도: O(n) +# - memoization을 하기 위한 dict에 최대 n의 개수만큼만 저장 +# - 재귀 호출 스택 깊이도 최대 n +class Solution: + # 해당 문제는 이전 계단까지 계산된 경우의 수를 찾아가며 현재 계단까지 오를 수 있는 경우의 수를 계산한다. + # n번째 계단까지 오를 수 있는 경우의 수는 (n-1번째까지 오를 수 있는 경우의 수) + (n-2번째까지 오를 수 있는 경우의 수)이다. + # 이는 동적 프로그래밍을 이용해 memoization을 하며 풀 수 있는 문제라고 할 수 있다. + def climbStairs(self, n: int) -> int: + memo = {} + + def dfs(n): + # n이 0 또는 1일 경우에는 하나의 방법만 있기 때문에 memo에 1을 넣고 return해준다. + if n <= 1: + memo[n] = 1 + return memo[n] + + # memoization을 이미한 경우에는 memo에서 결과를 꺼내 return해준다. + if n in memo: + return memo[n] + + # (n번째까지 오를 수 있는 경우의 수) = (n-1번째까지 오를 수 있는 경우의 수) + (n-2번째까지 오를 수 있는 경우의 수) + memo[n] = dfs(n - 1) + dfs(n - 2) + return memo[n] + + return dfs(n) diff --git a/product-of-array-except-self/liza0525.py b/product-of-array-except-self/liza0525.py index ab8e59c25..ffc156b14 100644 --- a/product-of-array-except-self/liza0525.py +++ b/product-of-array-except-self/liza0525.py @@ -20,3 +20,32 @@ def productExceptSelf(self, nums: List[int]) -> List[int]: answers[i] *= after_total_prod return answers + + +# 7기 풀이 +# 시간 복잡도: O(n) +# - nums의 길이만큼이 최대 연산 횟수가 된다. for문 두 번 돌린 것은 O(2n) -> O(n)으로 표기 가능 +# 공간 복잡도: O(n) +# - nums의 길이만큼 res를 만든다. +# - 단 이는 return variable이라서 리트코드에서는 공간 복잡도 계산에서 제외, 조건의 O(1)와 동일하다고 할 수 있다. +class Solution: + def productExceptSelf(self, nums: List[int]) -> List[int]: + # 정답에 대한 리스트 추가 + res = [1 for _ in range(len(nums))] + + previous_res = 1 + for i in range(len(nums)): + # i번째 인덱싀 이전까지의 값들을 res[i]에 먼저 곱해준 후, + # 자기 자신을 previous_res에 계산한다. 이는 다음 loop, 즉 i+1번째일 때 계산된 값을 그대로 곱하게 된다. + res[i] *= previous_res + previous_res *= nums[i] + + next_res = 1 + for i in range(len(nums) -1, -1, -1): + # 리스트의 맨 뒷쪽 index부터 계산해주면 i번째 인덱스 이후의 값들의 곱들을 계산할 수 있다. + # i번째 인덱싀 이후까지의 값들을 res[i]에 먼저 곱해준 후, + # 자기 자신을 next_res 계산한다. 이는 다음 loop, 즉 i-1번째일 때 계산된 값을 그대로 곱하게 된다. + res[i] *= next_res + next_res *= nums[i] + + return res diff --git a/valid-anagram/liza0525.py b/valid-anagram/liza0525.py index f86a319c6..e5ed6d571 100644 --- a/valid-anagram/liza0525.py +++ b/valid-anagram/liza0525.py @@ -23,3 +23,34 @@ def isAnagram(self, s: str, t: str) -> bool: if cnt != 0: return False return True + + + +# 7기 풀이 +# 시간 복잡도: O(n) +# - s, t, letter_dict를 모두 한 loop당 한 번 씩만 돈다. +# 공간 복잡도: O(n) +# - letter_dict 생성 시 s의 길이(n이라고 할 때)에 종속된다. +from collections import defaultdict + + +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + # s를 구성하는 각 알파벳의 개수를 저장할 defaultdict을 생성 + letter_dict = defaultdict(int) + + # s의 문자열을 돌며 각 알파벳의 개수를 카운트 + for ss in s: + letter_dict[ss] += 1 + + # t 문자열을 돌만셔 각 알파벳의 개수만큼 dictionary에서 빼준다. + for tt in t: + letter_dict[tt] -= 1 + + # s, t를 돌며 각 알파벳의 개수를 센 후 value들은 모두 0이 되어야 한다. (아나그램 특성 상) + # 각 알파벳의 value가 양수면 s에 더 많았고, 음수면 t가 더 많았다는 의미가 됨 + for v in letter_dict.values(): + if v != 0: + return False + + return True diff --git a/validate-binary-search-tree/liza0525.py b/validate-binary-search-tree/liza0525.py index 4967242e2..c65d16e4a 100644 --- a/validate-binary-search-tree/liza0525.py +++ b/validate-binary-search-tree/liza0525.py @@ -20,3 +20,31 @@ def inorder_tree(tree_node): return False return True + + +# 7기 풀이 +# 시간 복잡도: O(n) +# - 전체 트리 노드를 탐색하기 때문에 n +# 공간 복잡도: O(n) +# - 노드를 저장하는 리스트는 n의 길이만큼 생김 +class Solution: + # 중위 순회 결과가 strictly increasing인지 확인하는 방식 + def isValidBST(self, root: Optional[TreeNode]) -> bool: + tree_res = [] + + def inorder_search(node): + if node.left: + inorder_search(node.left) + + tree_res.append(node.val) + + if node.right: + inorder_search(node.right) + + inorder_search(root) + + for i in range(len(tree_res) - 1): + # i번째 값이 i + 1번째 값보다 크거나 같으면 strictly increasing하지 않다는 의미이므로 False를 리턴한다. + if tree_res[i] >= tree_res[i + 1]: + return False + return True