diff --git a/3sum/dusunax.py b/3sum/dusunax.py new file mode 100644 index 000000000..e277b41e5 --- /dev/null +++ b/3sum/dusunax.py @@ -0,0 +1,50 @@ +''' +# Leetcode 15. 3Sum + +use **two pointers** to solve this problem. + +## Time and Space Complexity + +``` +TC: O(n^2) +SC: O(1) +``` + +### TC is O(n^2): +- sorting the list = O(n log n) +- iterating through the list and using two pointers to find the sum of three numbers. = O(n^2) + +### SC is O(1): +- sorting in place = O(1) +''' + +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + nums.sort() # TC: O(n log n), SC: O(1) + result = [] # result are part of the output => do not count toward auxiliary (extra) space. + + for i in range(len(nums)): # TC: O(n^2) + if i > 0 and nums[i] == nums[i - 1]: + continue + + j = i + 1 + k = len(nums) - 1 + while j < k: + currSum = nums[i] + nums[j] + nums[k] + + if currSum < 0: + j += 1 + elif currSum > 0: + k -= 1 + else: + result.append([nums[i], nums[j], nums[k]]) + + while j < k and nums[j] == nums[j + 1]: + j += 1 + while j < k and nums[k] == nums[k - 1]: + k -= 1 + + j += 1 + k -= 1 + + return result diff --git a/climbing-stairs/dusunax.py b/climbing-stairs/dusunax.py new file mode 100644 index 000000000..c6501c180 --- /dev/null +++ b/climbing-stairs/dusunax.py @@ -0,0 +1,71 @@ +''' +# Leetcode 70. Climbing Stairs + +use `dynamic programming` to solve the problem. + +1. Bottom-up approach +2. Top-down approach + +## Time and Space Complexity + +### 1. Bottom-up approach + +``` +TC: O(n) +SC: O(1) +``` + +#### TC is O(n): +- iterating with a for loop. O(n) + +#### SC is O(1): +- using a constant space to store the previous two steps. O(1) + +### 2. Top-down approach + +``` +TC: O(n) +SC: O(n) +``` + +#### TC is O(n): +- performing a recursive call for each step. O(n) + +#### SC is O(n): +- using a memoization object to store the previous two steps. O(n) +''' + +class Solution: + ''' + 1. Bottom-up approach + ''' + def climbStairsLoop(self, n: int) -> int: + if n == 1 or n == 2: + return n + + # SC: O(1) + prev2 = 1 # ways to step 0 + prev1 = 2 # ways to step 1 + + for i in range(3, n + 1): # TC: O(n) + current = prev1 + prev2 # ways to (n-1) + (n-2) + prev2 = prev1 + prev1 = current + + return prev1 + + ''' + 2. Top-down approach + ''' + def climbStairsRecursive(self, n: int) -> int: + memo = {} # SC: O(n) + + def dp(step: int, memo: int) -> int: # TC: O(n) + if step == 1 or step == 2: + memo[step] = step + if step not in memo: + memo[step] = dp(step - 1, memo) + dp(step - 2, memo) # ways to (n-1) + (n-2) + return memo[step] + + return dp(n, memo) + diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/dusunax.py b/construct-binary-tree-from-preorder-and-inorder-traversal/dusunax.py new file mode 100644 index 000000000..5d3571e0c --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/dusunax.py @@ -0,0 +1,119 @@ + +''' +# Leetcode 105. Construct Binary Tree from Preorder and Inorder Traversal + +use **recursive** to solve this problem. + +## Time and Space Complexity + +### A. recursive & change range of preorder and inorder + +``` +TC: O(n) +SC: O(n) +``` + +### B. recursive & search index (of inorder) + +``` +TC: O(n^2) +SC: O(n) +``` + +### C. recursive & hash table + +``` +TC: O(n) +SC: O(n) +``` + +''' +class Solution: + ''' + A. 재귀 풀이 + preorder와 inorder의 각각의 범위를 조정하여 트리를 생성 + ''' + def buildTreeA(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + def setTree(pre_left, pre_right, in_left, in_right): + # 재귀 종료 조건: preorder 범위가 유효하지 않은 경우 + if pre_left > pre_right: + return None + + val = preorder[pre_left] # preorder의 현재 루트 노드 값 가져오기 + mid = TreeNode(val) # 루트 노드를 먼저 생성 + + mid_inorder = inorder_idx_map[val] # 루트 노드의 inorder 인덱스 가져오기 + left_size = mid_inorder - in_left # 왼쪽 서브트리의 크기 계산 + + # 왼쪽 서브트리 생성: preorder와 inorder의 범위를 왼쪽 서브트리로 조정 + mid.left = setTree( + pre_left + 1, pre_left + left_size, in_left, mid_inorder - 1 + ) + + # 오른쪽 서브트리 생성: preorder와 inorder의 범위를 오른쪽 서브트리로 조정 + mid.right = setTree( + pre_left + left_size + 1, pre_right, mid_inorder + 1, in_right + ) + + return mid # 현재 노드 반환 + + # inorder를 값 -> 인덱스 맵핑한 딕셔너리 생성 - TC: O(n), SC: O(n) + inorder_idx_map = {value: idx for idx, value in enumerate(inorder)} + + # 트리 생성 시작 (preorder와 inorder 전체 범위 사용) - TC: O(n), SC: O(n) + return setTree(0, len(preorder) - 1, 0, len(inorder) - 1) + + + ''' + # B. 재귀 풀이 + 공간 최적화 + # 레퍼런스 링크의 풀이 2: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ + # 특징: 순회 시마다 인덱스를 찾는 과정이 있음 + ''' + def buildTreeB(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + # pre: 현재 preorder에서 확인할 인덱스 + # start, end: inorder에서 사용할 시작/종료 범위 + def setTree(pre, start, end): + # 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우 + if not (pre < len(preorder) and start <= end): # preorder에서 확인할 인덱스가 범위에서 나감, 투 포인터가 만남 + return None + + val = preorder[pre] # 현재 노드의 값 + root = inorder.index(val) # 트리/서브트리의 루트 노드 인덱스 찾기 - TC: O(n) + + left = setTree(pre + 1, start, root - 1) + # inorder에서 root노드의 왼쪽은 왼쪽 서브트리 + # pre의 변화: 왼쪽 서브트리의 루트 노드를 찾기 위해 +1 이동 + + right = setTree(pre + 1 + root - start, root + 1, end) + # inorder에서 root노드의 오른쪽은 오른쪽 서브트리 + # pre의 변화: 오른쪽 서브트리의 루트 노드를 찾기 위해 +1 이동 + (root - start) 👈 왼쪽 서브트리의 크기 만큼 더 이동 + + return TreeNode(preorder[pre], left, right) # 트리 노드 생성 + + # preorder 최초 인덱스 = 루트 노드(0), inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스 + return setTree(0, 0, len(inorder) - 1) # TC: O(n^2), SC: O(n) + + ''' + C. 재귀 풀이 + 시간 최적화 + 레퍼런스 링크의 풀이 3: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ + 특징: A에서 preorder를 찾는 O(n) 과정을 해시 테이블을 사용하여 O(1)로 최적화 + ''' + def buildTreeC(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + # enumerate: 인덱스와 값을 동시에 반환 + # inorder를 val -> idx로 매핑한 딕셔너리 생성 + inorder_index_map = {val: idx for idx, val in enumerate(inorder)} + # preorder를 순회하기 위한 iterator 객체 생성 + pre_iter = iter(preorder) + + def setTree(start, end): + if start > end: # 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우 + return None + + root_val = next(pre_iter) # 현재 노드의 값, 매 순회마다 다음 preorder 노드(root)의 값을 가져옴 + root = inorder_index_map[root_val] # 트리/서브트리의 루트 노드 인덱스를 O(1) 시간으로 찾기 + + left = setTree(start, root - 1) # 왼쪽 서브트리 + right = setTree(root + 1, end) # 오른쪽 서브트리 + return TreeNode(root_val, left, right) # 트리 노드 생성 + + return setTree(0, len(inorder) - 1) # inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스 diff --git a/decode-ways/dusunax.py b/decode-ways/dusunax.py new file mode 100644 index 000000000..dda61ca05 --- /dev/null +++ b/decode-ways/dusunax.py @@ -0,0 +1,56 @@ +''' +# Leetcode 91. Decode Ways + +use **dynamic programming** to solve this problem. + +## Time and Space Complexity + +``` +TC: O(n) +SC: O(n) +``` + +### TC is O(n): +- iterating through the string and checking if the current character is decodable. = O(n) + +### SC is O(n): +- creating a dp array of size n + 1 = O(n) +''' +class Solution: + def isDecodable(self, str: str): + return 1 <= int(str) <= 26 and str[0] != '0' + + def numDecodings(self, s: str) -> int: + if s[0] == "0": + return 0 + + n = len(s) + dp = (n + 1) * [0] + dp[0] = 1 + dp[1] = 1 + + for i in range(2, n + 1): + one = s[i - 1] + two = s[i - 2:i] + + if self.isDecodable(one): + dp[i] += dp[i - 1] + if self.isDecodable(two): + dp[i] += dp[i - 2] + + return dp[n] + +''' +# sudo code +- 헬퍼함수: 0으로 시작하지 않고, 1~26인 경우 True +- numDecodings함수 + 1. n: 문자열 s의 길이 + 2. dp: 결과를 저장할 배열, n+1 + 3. BaseCase: dp[0] = 1, dp[1] = 1 + 4. for loop 2 to n: + one = s의 i-1 위치의 1글자 (현재 글자) + two = s의 i-2부터 i까지 자른 2글자 (현재 글자 포함 이전 글자) + if one is decodable => dp[i] += dp[i - 1] i길이일 때, dp의 -1 경우의 만큼수 추가 (현재 글자를 한 글자로 해석) + if two is decodable => dp[i] += dp[i - 2] i길이일 때, dp의 -2 경우의 수 만큼 추가 (현재 글자를 두 글자로 해석) + 5. dp[n] 반환: 최종 디코드 가능한 경우의 수 결과 +''' diff --git a/valid-anagram/dusunax.py b/valid-anagram/dusunax.py new file mode 100644 index 000000000..bf785ee8a --- /dev/null +++ b/valid-anagram/dusunax.py @@ -0,0 +1,44 @@ +''' +# Leetcode 242. Valid Anagram + +use `Counter` to (1)compare the frequency of characters in both strings, and (2)try to compare the frequency more efficiently. 🔍 + +## Time and Space Complexity + +``` +TC: O(n) +SC: O(n) +``` + +### A. use frequency object + +#### TC is O(n): +- iterating through the strings just once to compare the frequency of characters. O(n) + +#### SC is O(n): +- creating a new string `converted_s` to store the + +### B. use Counter more efficiently + +#### TC is O(n): +- iterating through the strings just once to compare the frequency of characters. O(n) + +#### SC is O(n): +- creating a new string `converted_s` to store the +''' +class Solution: + def isAnagramA(self, s: str, t: str) -> bool: + if len(s) != len(t): + return False + + frequency = Counter(s) # SC: O(n) + + for char in t: # TC: O(n) + if char not in frequency or frequency[char] == 0: # TC: O(1) + return False + frequency[char] -= 1 + + return True + + def isAnagramB(self, s: str, t: str) -> bool: + return Counter(s) == Counter(t) # TC: O(n), SC: O(n)