diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/haklee.py b/construct-binary-tree-from-preorder-and-inorder-traversal/haklee.py new file mode 100644 index 000000000..771e6e9ac --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/haklee.py @@ -0,0 +1,98 @@ +"""TC: O(n), SC: O(n) + +아이디어: +- preorder 트리가 주어져 있다면 다음과 같이 분할할 수 있다. + - [root값, [...left], [...right]] + - 위의 left, right는 preorder 트리와 같은 방식으로 구성된다. +- inorder 트리가 주어져 있다면 다음과 같이 분할할 수 있다. + - [[...left], root값, [...right]] + - 위의 left, right는 inorder 트리와 같은 방식으로 구성된다. + - 이때, + - left의 첫 아이템이 인덱스 inorder_s에 있고, + - right의 마지막 아이템이 인덱스 inorder_e - 1에 있다고 하자. + - 즉, inorder_e를 미포함! +- preorder 트리의 맨 앞 값을 통해 root값 val을 찾고, 이 값으로 inorder의 root값의 인덱스를 찾을 수 있다. + - 모든 node의 val값이 unique한 것이 조건으로 주어져 있으므로 val값의 indices를 전처리해둘 수 있다. + - 이때, inorder의 root값의 인덱스를 inorder_root이라고 하자. +- inorder의 root값의 위치와 inorder 트리의 시작 위치를 알 수 있다면 + [...left]의 길이 left_len을 알 수 있다. + - left_len = inorder_root - inorder_start +- preorder 트리의 left의 루트는 [...left]의 첫 아이템, 즉, preorder_root에 1을 더한 값이다. +- preorder 트리의 right의 루트는 [...right]의 첫 아이템, 즉, preorder_root + 1 + left_len이다. +- root값을 구할 수 없으면 노드가 없다. + - inorder_s >= inorder_e와 같이 판별이 가능하다. 즉, 아이템이 하나도 없는 경우. + +위의 아이디어를 종합하면, +- preorder 트리의 루트 인덱스 preorder_root가 주어진, 구간 (inorder_s, inorder_e)에서 정의된 inorder 트리는 + - val값은 preorder[preorder_root]이 된다. + - left node는 아래와 같이 구해진다. + - preorder 트리의 루트 인덱스 preorder_root + 1, + - 구간 (inorder_s, inorder_root) + - 이때 구간이 유효하지 않으면 노드가 없다. + - right node는 아래와 같이 구해진다. + - preorder 트리의 루트 인덱스 preorder_root + 1 + left_len, + - 구간 (inorder_root + 1, inorder_end) + - 이때 구간이 유효하지 않으면 노드가 없다. + + +SC: +- 처음 inorder_indices를 계산할때 O(n). +- 아래의 build함수 호출이 최대 트리의 깊이만큼 재귀를 돌면서 쌓일 수 있다. + - 트리의 깊이는 최악의 경우 O(n). + +TC: +- build함수는 O(1). 코드 참조. +- 위의 과정을 n개의 노드에 대해 반복하므로 O(n). +""" + + +# 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]: + inorder_indices = {v: i for i, v in enumerate(inorder)} + + def build(inorder_s, inorder_e, preorder_root): + if inorder_s >= inorder_e: # O(1) + return None + val = preorder[preorder_root] # O(1) + inorder_root = inorder_indices[val] # O(1) + left_len = inorder_root - inorder_s # O(1) + return TreeNode( + val, + left=build(inorder_s, inorder_root, preorder_root + 1), + right=build(inorder_root + 1, inorder_e, preorder_root + 1 + left_len), + ) + + return build(0, len(inorder), 0) + + +""" +그런데 위의 아이디어를 다시 생각해보면, 모든 노드들을 preorder 순서로 순회한다! +- `val = preorder[preorder_root]`와 같은 방식으로 val값을 구하지 않고, 주어진 preorder를 순서대로 가져와도 됨. +- 즉, preorder를 iterator로 바꿔서 next를 통해 값을 하나씩 뽑아와서 건네줘도 된다. +- 이렇게 하면 build함수에 preorder_root를 전달하지 않아도 됨. +""" + + +class Solution: + def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]: + inorder_indices = {v: i for i, v in enumerate(inorder)} + preorder_iter = iter(preorder) + + def build(inorder_s, inorder_e): + if inorder_s >= inorder_e: # O(1) + return None + val = next(preorder_iter) # O(1) + inorder_root = inorder_indices[val] # O(1) + return TreeNode( + val, + left=build(inorder_s, inorder_root), + right=build(inorder_root + 1, inorder_e), + ) + + return build(0, len(inorder)) diff --git a/counting-bits/haklee.py b/counting-bits/haklee.py new file mode 100644 index 000000000..418bf296c --- /dev/null +++ b/counting-bits/haklee.py @@ -0,0 +1,35 @@ +"""TC: O(n), SC: O(n) + +아이디어: +- bin으로 변환한 값의 길이가 k인 모든 수들에 대한 bit_count값을 알고 있다고 하자. +- 사실 위의 수들은 bin으로 변환했을때 맨 앞 자리가 0으로 시작하는 길이 k+1의 수라고 볼 수 있다. +- 그렇다면 bin으로 변환했을때 맨 앞 자리가 1로 시작하는 길이 k+1인 수들의 bit_count는 + 맨 앞 자리가 0으로 시작하는 수들의 bit_count에 1을 더한 것이라고 할 수 있다. +- 위의 아이디어를 활용하면 앞 2^k 수들의 bit_count를 알고 있으면 간단한 더하기 연산을 통해 + 이후 2^k 수들의 bit_count값도 알 수 있다. + e.g.) + - 0, 1의 bit_count가 [0, 1]이라면, 2, 3의 bit_count는 [0+1, 1+1], 즉, 0~3의 bit_count는 [0, 1, 1, 2] + - 0~3의 bit_count가 [0, 1, 1, 2]라면, 4~7의 bit_count는 [1, 2, 2, 3], 즉, 0~7의 bit_count는 + [0, 1, 1, 2, 1, 2, 2, 3] + - ... +- 리스트의 크기를 2배씩 늘리다가 n보다 커졌을때 앞 n개의 아이템만 취해서 리턴. + + +SC: +- 아래에서 리스트 s의 길이는 2^(k-1) < n <= 2^k를 만족하는 2^k만큼 커진다. +- 즉, O(n). + +TC: +- s 안에 들어있는 i번째 아이템을 계산할때 필요한 연산은 덧셈 1회, 즉, O(1). +- i번째 아이템 값을 구하기 위해 그 앞의 값을 미리 계산해둔 것이라 생각할 수 있다. +- SC 분석과 비슷하게, 2^(k-1) < n <= 2^k를 만족하는 2^k만큼 반복. 즉, O(n). +""" + + +class Solution: + def countBits(self, n: int) -> List[int]: + s = [0] + m = n * 2 + while m := m >> 1: + s += [i + 1 for i in s] + return s[: n + 1] diff --git a/decode-ways/haklee.py b/decode-ways/haklee.py new file mode 100644 index 000000000..74e55927d --- /dev/null +++ b/decode-ways/haklee.py @@ -0,0 +1,32 @@ +"""TC: O(n), SC: O(1) + +아이디어: +뒷 k개의 글자를 디코딩 하는 경우의 수를 f(k)라고 하자. +f(k)는 다음의 두 경우의 수를 더한 값이다. + - 뒷 k개의 글자 중 첫 글자가 디코딩 가능한 경우, 뒷 k-1글자를 디코딩하는 경우의 수 + - 뒷 k개의 글자 중 앞 두 글자가 디코딩 가능한 경우, 뒷 k-2글자를 디코딩하는 경우의 수 +즉, f(k) = (앞 두 글자 판별)*f(k-2) + (앞 한 글자 판별)*f(k-1) + + +SC: +- tabulation 과정에서 값 2개만 계속 유지한다. +- 즉, O(1). + +TC: +- f(k) 구하는 식: O(1) + - 두 글자가 디코딩 가능한지 판별: O(1) + - 첫 글자가 디코딩 가능한지 판별: O(1) +- 위의 f(k)를 구하는 것을 s의 길이에서 2를 뺀 수만큼 루프, 즉, O(n) +- 종합하면 O(n). +""" + + +class Solution: + def numDecodings(self, s: str) -> int: + # init + x, y = 1, int(int(s[-1]) != 0) # f(0), f(1) + # tabulation + for i in range(len(s) - 2, -1, -1): # 뒷 k개 글자의 시작 글자가 s[i] + # f(k-2), f(k-1)을 f(k-1), f(k)로 + x, y = y, (x * (10 <= int(s[i : i + 2]) <= 26)) + (y * (int(s[i]) != 0)) + return y diff --git a/encode-and-decode-strings/haklee.py b/encode-and-decode-strings/haklee.py new file mode 100644 index 000000000..6b25b4d06 --- /dev/null +++ b/encode-and-decode-strings/haklee.py @@ -0,0 +1,75 @@ +""" +encode: TC: O(n), SC: O(l) +decode: TC: O(n), SC: O(l) + +input으로 들어온 string들의 길이를 전부 더한 값을 l이라고 하자. +input에 들어있는 아이템 개수를 n이라고 하자. + +아이디어: +- 인풋을 처리한 값 앞쪽에 인풋을 해석하기 위한 정보를 두자. + - 즉, 인풋을 처리한 값을 일종의 body로 보고 앞에 header를 붙이는 접근. + - header는 body의 값이 어떻게 들어와도 영향을 받지 않는다! +- 다음과 같이 encode를 한다. + - encode한 string은 f'{header}:{body}'형식으로 되어있다. + - body에는 input에 있는 값을 바로 concat한 값을 쓴다. + - header에는 input에 있는 string의 길이를 ','로 구분한 값을 쓴다. + - e.g.) + body: ['a', 'bc', 'd'] -> 'abcd' + header: ['a', 'bc', 'd'] -> '1,2,1' + encoded string: '1,2,1:abcd' +- 다음과 같이 decode를 한다. + - 첫 번째로 등장하는 ':'을 찾아서 split한다. + - 앞 부분이 header, 뒷 부분이 body다. + - header를 다음과 같이 처리한다. + - ','로 split해서 `x: List[int]`를 얻는다. + - body를 다음과 같이 처리한다. + - body를 앞서 구한 x에 들어있는 길이로 쭉 쪼개면 된다. + - x의 누적합을 구하면서 이를 시작 인덱스로 활용한다. + +SC: +- encode + - body의 길이는 l이다. 즉, O(l). + - header의 길이는 최악의 경우 O(l)이다. + - input의 모든 글자가 길이 1일때, 최악의 경우 O(l). + - input의 모든 글자가 한 단어일때, 최고의 경우 O(log l). + - 종합하면 O(l) + O(l)이라 O(l)이다. +- decode + - 전체 메시지의 body에 들어있는 값을 쪼개서 리스트로 만드는 것이므로 O(l). + - 길이 값을 split해서 리스트로 만든다. O(n) + - 종합하면 O(l + n)인데, 이때 n은 무조건 l 이하이므로 O(l). + +TC: +- encode + - n개의 아이템을 순회하면서 길이를 얻는다. O(n). +- decode + - 길이 값을 split해서 리스트로 만든다. O(n). + - 누적합을 활용하여 길이 값 리스트를 한 번 순회. O(n). + - 종합하면 O(n). +""" + + +class Solution: + """ + @param: strs: a list of strings + @return: encodes a list of strings to a single string. + """ + + def encode(self, strs): + body = "".join(strs) + header = ",".join([str(len(i)) for i in strs]) + return header + ":" + body + + """ + @param: str: A string + @return: decodes a single string to a list of strings + """ + + def decode(self, str): + header, body = str.split(":", 1) + len_list = [int(i) for i in header.split(",")] + start_ind = 0 + result = [] + for i in len_list: + result.append(body[start_ind : start_ind + i]) + start_ind += i + return result diff --git a/valid-anagram/haklee.py b/valid-anagram/haklee.py new file mode 100644 index 000000000..0226336c9 --- /dev/null +++ b/valid-anagram/haklee.py @@ -0,0 +1,19 @@ +"""TC: O(n), SC: O(n) + +여기서 n은 s, t의 길이값 중 큰 것이라 가정. + +SC: +- Counter는 s, t에 들어있는 글자들을 key로 하는 dict. 즉, SC는 O(n). + +TC: +- s, t에 들어있는 글자들을 key로 dict를 업데이트를 한 번 할때 O(1). +- 위의 과정을 길이 n만큼 반복하므로 O(n). +""" + +from collections import Counter + + +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + # return sorted(s) == sorted(t) # TC: O(n log n), SC: O(n) + return Counter(s) == Counter(t) # TC: O(n), SC: O(n)