From 35e0c547d70cfd9ed35b47c937de7d12c233a838 Mon Sep 17 00:00:00 2001 From: thispath98 Date: Wed, 18 Dec 2024 10:30:03 +0900 Subject: [PATCH 1/5] feat: Add Valid Anagram solutions --- valid-anagram/thispath98.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 valid-anagram/thispath98.py diff --git a/valid-anagram/thispath98.py b/valid-anagram/thispath98.py new file mode 100644 index 000000000..b466a0189 --- /dev/null +++ b/valid-anagram/thispath98.py @@ -0,0 +1,31 @@ + +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + """ + Time Complexity: + O(N log N): + 두 string을 정렬, 이는 보통 quick sort로 구현되어 + N log N 만큼 소요된다. + + Space Complexity: + O(N): + 최악의 경우 (모든 string이 유일할 경우) N개의 리스트 + 를 저장한다. + """ + return sorted(s) == sorted(t) + + def isAnagramCounter(self, s: str, t: str) -> bool: + """ + Time Complexity: + O(N): + 해시를 기반으로 일치 여부 탐색, N개의 엔트리를 + 한번씩 순회하는 것으로 구현된다. + + Space Complexity: + O(N): + 최악의 경우 (모든 string이 유일할 경우) N개의 리스트 + 를 저장한다. + """ + from collections import Counter + + return Counter(s) == Counter(t) From 0eb2ff99034528dd0b954119d7d5e77b6a9f8211 Mon Sep 17 00:00:00 2001 From: thispath98 Date: Wed, 18 Dec 2024 10:44:59 +0900 Subject: [PATCH 2/5] feat: Add Climbing Stairs --- climbing-stairs/thispath98.py | 87 +++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 climbing-stairs/thispath98.py diff --git a/climbing-stairs/thispath98.py b/climbing-stairs/thispath98.py new file mode 100644 index 000000000..9716f108d --- /dev/null +++ b/climbing-stairs/thispath98.py @@ -0,0 +1,87 @@ +class Solution: + def climbStairsFact(self, n: int) -> int: + """ + Intuition: + 1 + 1 + ... + 1 는 2가 0개일 때 n을 만들 수 있는 경우의 수 = 1. + 1 + 2 + ... + 1 는 2가 1개일 때 n을 만들 수 있는 경우의 수 = (n-1)C1. + 2 + 2 + ... + 1 는 2가 2개일 때 n을 만들 수 있는 경우의 수 = (n-2)C2. + ... + 즉, n이 0부터 최대로 놓을 수 있는 값(two_cnt)까지 + 1로 놓여져 있는 배열에서 2의 위치를 선택(조합)하는 것과 같다. + + Time Complexity: + O(N^2 log N): + (n-i)Ci는 O((N - i) log i)이고, i가 0부터 two_cnt까지 증가할 경우 + 대략 O(N log N)로 계산한다. + 이를 two_cnt(N//2) 까지 반복하므로, O(N^2 log N). + + Space Complexity: + O(1): + answer를 업데이트 해가며 값 계산. + """ + import math + + two_cnt = n // 2 + answer = 1 + + for i in range(1, two_cnt + 1): + # (n - i)Ci + # 여기서 int로 형변환 할 경우 수치적 불안정 발생 + answer += math.factorial(n - i) / math.factorial(n - 2 * i) / math.factorial(i) + + return int(answer) # int로 형변환 하지 않을 경우 TypeError + + def climbStairsComb(self, n: int) -> int: + """ + Intuition: + `climbStairsFact`에서 Factorial은 수치적 불안정성으로 인해 + 더욱 안정적인 math.comb를 사용한다. + + Time Complexity: + O(N^2 log N): + (n-i)Ci는 O((N - i) log i)이고, i가 0부터 two_cnt까지 증가할 경우 + 대략 O(N log N)로 계산한다. + 이를 two_cnt(N//2) 까지 반복하므로, O(N^2 log N). + + Space Complexity: + O(1): + answer를 업데이트 해가며 값 계산. + """ + import math + + two_cnt = n // 2 + answer = 1 + + for i in range(1, two_cnt + 1): + # (n - i)Ci + # math.comb 메소드는 수치적으로 안정적으로 계산해준다 + answer += math.comb(n - i, i) + return answer + + def climbStairsFib(self, n: int) -> int: + """ + Intuition: + climb stairs 문제는 대표적인 피보나치 수열 문제이다. + 실제로 경우의 수를 계산해보면, + 1 -> 1 + 2 -> 2 + 3 -> 3 + 4 -> 5 + 5 -> 8 + ... + 로 피보나치 수열을 따르는 것을 알 수 있다. + + Time Complexity: + O(N): + N번 순회하여 피보나치 수열 구현. + + Space Complexity: + O(N): + N개 만큼 해시 key-value 쌍을 저장. + """ + fib_dict = {1: 1, 2: 2, 3: 3} + for i in range(1, n + 1): + if i not in fib_dict: + fib_dict[i] = fib_dict[i - 1] + fib_dict[i - 2] + if i == n: + return fib_dict[i] From c2e4b6c3d36cf269ce4c43eb3f5f60f63c598b6f Mon Sep 17 00:00:00 2001 From: thispath98 Date: Thu, 19 Dec 2024 23:57:59 +0900 Subject: [PATCH 3/5] feat: Add 3 Sum solutions --- 3sum/thispath98.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 3sum/thispath98.py diff --git a/3sum/thispath98.py b/3sum/thispath98.py new file mode 100644 index 000000000..6699f307e --- /dev/null +++ b/3sum/thispath98.py @@ -0,0 +1,75 @@ +class Solution: + def threeSumSet(self, nums: List[int]) -> List[List[int]]: + """ + Intuition: + 두 값을 더하고, 세트 안에 값에서 0을 만족시키는 값이 있을 경우 + 정답에 추가한다. + 세트(해시)의 접근이 O(1) 임을 이용한다. + + Time Complexity: + O(N^2): + 2중 for문이 있으므로, O(N^2)이다. + for문 내부에 시간복잡도에 영향을 줄만한 코드는 없으며 + 정렬은 O(3 log 3)이므로 무시 가능하다. + + Space Complexity: + O(N): + 중복된 값이 없다면 seen 세트는 N - 1개의 값을 저장한다. + + Key takeaway: + 해시를 이용한 풀이는 잘 못풀어서 조금 더 연습해야겠다. + 또한, 리스트를 해시하기 위해 tuple로 변환하는 것에 대해서 처음 알았다. + """ + answer = set() + for i in range(len(nums) - 2): + seen = set() + for j in range(i + 1, len(nums)): + complement = -(nums[i] + nums[j]) + if complement in seen: + answer.add(tuple(sorted([nums[i], nums[j], complement]))) + seen.add(nums[j]) + + return list(answer) + + +class Solution: + def threeSumTwoPointer(self, nums: List[int]) -> List[List[int]]: + """ + Intuition: + i를 for문으로 증가시키면서, 매 iteration마다 two pointer를 사용한다. + + Time Complexity: + O(N^2): + 2중 for문이 있으므로, O(N^2)이다. + for문 내부에 시간복잡도에 영향을 줄만한 코드는 없으며 + 정렬은 O(3 log 3)이므로 무시 가능하다. + + Space Complexity: + O(1): + 포인터 3개만을 사용하므로, 공간 복잡도는 O(1)이다. + + Key takeaway: + 투포인터를 응용한 문제임을 떠올리긴 했으나, + nested two pointer임을 인지하지 못했다. + 이러한 경우에도 더 고민을 해봐야겠다. + """ + nums.sort() + + answer = set() + for i in range(len(nums) - 2): + # 만약 i가 이전의 값과 중복된 값이라면 이 작업은 필요 없다. + if i > 0 and nums[i] == nums[i - 1]: + continue + + j = i + 1 + k = len(nums) - 1 + while j < k: + if nums[i] + nums[j] + nums[k] == 0: + answer.add(tuple(sorted([nums[i], nums[j], nums[k]]))) + j += 1 + elif nums[i] + nums[j] + nums[k] > 0: + k -= 1 + else: + j += 1 + + return list(answer) From 687f74f24b267269029d2f51394c356574b82291 Mon Sep 17 00:00:00 2001 From: thispath98 Date: Sat, 21 Dec 2024 16:52:24 +0900 Subject: [PATCH 4/5] feat: Add Construct Binary Tree From Preorder And Inorder Traversal solutions --- .../thispath98.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 construct-binary-tree-from-preorder-and-inorder-traversal/thispath98.py diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/thispath98.py b/construct-binary-tree-from-preorder-and-inorder-traversal/thispath98.py new file mode 100644 index 000000000..f58ab1014 --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/thispath98.py @@ -0,0 +1,45 @@ +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + """ + Intuition: + preorder 트리의 첫번째 원소는 항상 루트이다. + 또한, inorder 트리에서 루트를 기준으로 왼쪽은 left child, + 오른쪽은 right child를 의미한다. + 따라서 이를 이용해 재귀적으로 호출한다. + + Time Complexity: + O(N^2): + parent_idx를 선택하는 데에 O(N)이 소요되고 + 최악의 경우 N번 재귀 호출해야 하므로 O(N^2)이다. + + Space Complexity: + O(N): + TreeNode는 N개의 값을 저장한다. + + Key takeaway: + 리트코드에서 클래스를 반환하는 문제는 다음처럼 하는 것을 + 처음 알게 되었다. + """ + if not preorder: + return None + + parent = preorder[0] + parent_idx = inorder.index(parent) # O(N) + + left_pre = preorder[1 :parent_idx + 1] + left_in = inorder[:parent_idx] + left = self.buildTree(left_pre, left_in) + + right_pre = preorder[1 + parent_idx:] + right_in = inorder[1 + parent_idx:] + right = self.buildTree(right_pre, right_in) + + tree = TreeNode(parent, left, right) + + return tree From 03ef4b97d85b541e2c5eef7849c37f18e3d645db Mon Sep 17 00:00:00 2001 From: thispath98 Date: Sat, 21 Dec 2024 16:52:39 +0900 Subject: [PATCH 5/5] feat: Add Decode Ways soultions --- decode-ways/thispath98.py | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 decode-ways/thispath98.py diff --git a/decode-ways/thispath98.py b/decode-ways/thispath98.py new file mode 100644 index 000000000..a2af20e86 --- /dev/null +++ b/decode-ways/thispath98.py @@ -0,0 +1,69 @@ +class Solution: + def numDecodings(self, s: str) -> int: + """ + Intuition: + 문자를 쪼개서 디코드 조합을 얻는다. + 이후, 디코드 조합에서 2개씩 묶을 경우 26보다 큰 수가 있을 수 있으므로 + 다시 한번 쪼갠다. + 마지막으로 각 디코드 조합에서 값을 얻는 경우는 + 피보나치 수열을 따른다. + + Time Complexity: + O(N): + 문자를 쪼개고, 묶는 문자 조합을 구하고, + 피보나치 수열에서 값을 찾는 것은 모두 O(N)만큼 소요된다. + + Space Complexity: + O(1): + 최악의 경우 N개에 대한 피보나치 수열을 구해야 하고, + N은 최대 100이므로 O(1)이다. + """ + if s[0] == "0": + return 0 + + # 문자열에서 0 앞에 1 혹은 2가 붙는지 확인 + # 그렇지 않다면, 디코드 할 수 없으므로 return 0 + # O(N) + splitted_s = [] + start = 0 + for i in range(len(s)): + if s[i] == "0": + if s[i - 1] in "12": + splitted_s.append(s[start: i - 1]) + start = i + 1 + else: + return 0 + splitted_s.append(s[start:]) + + # 쪼개진 문자에서 두 문자를 보고, 묶을 수 있는지 + # (26 이하인지)를 확인한다. + # 묶을 수 없다면, 문자를 다시 한번 쪼갠다. + # O(N) + interval = [] + for splitted in splitted_s: + start = 0 + for i in range(1, len(splitted)): + if int(splitted[i - 1: i + 1]) > 26: + interval.append(i - start) + start = i + + interval.append(len(splitted) - start) + + answer = 1 + fib_dict = {0: 1, 1: 1, 2: 2} + + + def get_fib(n): + if n not in fib_dict: + fib_dict[n] = get_fib(n - 1) + get_fib(n - 2) + return fib_dict[n] + + + # 쪼개진 문자에서 디코드 조합은 + # 문자 개수를 피보나치 수열에 넣은 값이다. + # 이 값들은 쪼개진 문자들에 대하여 곱셈으로 계산된다. + # O(N) + get_fib(max(interval)) + for n in interval: + answer *= fib_dict[n] + return answer