Skip to content

Commit 171d243

Browse files
authored
Merge pull request DaleStudy#342 from haklee/main
[haklee] week 2
2 parents 2397584 + c554369 commit 171d243

File tree

5 files changed

+259
-0
lines changed

5 files changed

+259
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""TC: O(n), SC: O(n)
2+
3+
아이디어:
4+
- preorder 트리가 주어져 있다면 다음과 같이 분할할 수 있다.
5+
- [root값, [...left], [...right]]
6+
- 위의 left, right는 preorder 트리와 같은 방식으로 구성된다.
7+
- inorder 트리가 주어져 있다면 다음과 같이 분할할 수 있다.
8+
- [[...left], root값, [...right]]
9+
- 위의 left, right는 inorder 트리와 같은 방식으로 구성된다.
10+
- 이때,
11+
- left의 첫 아이템이 인덱스 inorder_s에 있고,
12+
- right의 마지막 아이템이 인덱스 inorder_e - 1에 있다고 하자.
13+
- 즉, inorder_e를 미포함!
14+
- preorder 트리의 맨 앞 값을 통해 root값 val을 찾고, 이 값으로 inorder의 root값의 인덱스를 찾을 수 있다.
15+
- 모든 node의 val값이 unique한 것이 조건으로 주어져 있으므로 val값의 indices를 전처리해둘 수 있다.
16+
- 이때, inorder의 root값의 인덱스를 inorder_root이라고 하자.
17+
- inorder의 root값의 위치와 inorder 트리의 시작 위치를 알 수 있다면
18+
[...left]의 길이 left_len을 알 수 있다.
19+
- left_len = inorder_root - inorder_start
20+
- preorder 트리의 left의 루트는 [...left]의 첫 아이템, 즉, preorder_root에 1을 더한 값이다.
21+
- preorder 트리의 right의 루트는 [...right]의 첫 아이템, 즉, preorder_root + 1 + left_len이다.
22+
- root값을 구할 수 없으면 노드가 없다.
23+
- inorder_s >= inorder_e와 같이 판별이 가능하다. 즉, 아이템이 하나도 없는 경우.
24+
25+
위의 아이디어를 종합하면,
26+
- preorder 트리의 루트 인덱스 preorder_root가 주어진, 구간 (inorder_s, inorder_e)에서 정의된 inorder 트리는
27+
- val값은 preorder[preorder_root]이 된다.
28+
- left node는 아래와 같이 구해진다.
29+
- preorder 트리의 루트 인덱스 preorder_root + 1,
30+
- 구간 (inorder_s, inorder_root)
31+
- 이때 구간이 유효하지 않으면 노드가 없다.
32+
- right node는 아래와 같이 구해진다.
33+
- preorder 트리의 루트 인덱스 preorder_root + 1 + left_len,
34+
- 구간 (inorder_root + 1, inorder_end)
35+
- 이때 구간이 유효하지 않으면 노드가 없다.
36+
37+
38+
SC:
39+
- 처음 inorder_indices를 계산할때 O(n).
40+
- 아래의 build함수 호출이 최대 트리의 깊이만큼 재귀를 돌면서 쌓일 수 있다.
41+
- 트리의 깊이는 최악의 경우 O(n).
42+
43+
TC:
44+
- build함수는 O(1). 코드 참조.
45+
- 위의 과정을 n개의 노드에 대해 반복하므로 O(n).
46+
"""
47+
48+
49+
# Definition for a binary tree node.
50+
# class TreeNode:
51+
# def __init__(self, val=0, left=None, right=None):
52+
# self.val = val
53+
# self.left = left
54+
# self.right = right
55+
class Solution:
56+
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
57+
inorder_indices = {v: i for i, v in enumerate(inorder)}
58+
59+
def build(inorder_s, inorder_e, preorder_root):
60+
if inorder_s >= inorder_e: # O(1)
61+
return None
62+
val = preorder[preorder_root] # O(1)
63+
inorder_root = inorder_indices[val] # O(1)
64+
left_len = inorder_root - inorder_s # O(1)
65+
return TreeNode(
66+
val,
67+
left=build(inorder_s, inorder_root, preorder_root + 1),
68+
right=build(inorder_root + 1, inorder_e, preorder_root + 1 + left_len),
69+
)
70+
71+
return build(0, len(inorder), 0)
72+
73+
74+
"""
75+
그런데 위의 아이디어를 다시 생각해보면, 모든 노드들을 preorder 순서로 순회한다!
76+
- `val = preorder[preorder_root]`와 같은 방식으로 val값을 구하지 않고, 주어진 preorder를 순서대로 가져와도 됨.
77+
- 즉, preorder를 iterator로 바꿔서 next를 통해 값을 하나씩 뽑아와서 건네줘도 된다.
78+
- 이렇게 하면 build함수에 preorder_root를 전달하지 않아도 됨.
79+
"""
80+
81+
82+
class Solution:
83+
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
84+
inorder_indices = {v: i for i, v in enumerate(inorder)}
85+
preorder_iter = iter(preorder)
86+
87+
def build(inorder_s, inorder_e):
88+
if inorder_s >= inorder_e: # O(1)
89+
return None
90+
val = next(preorder_iter) # O(1)
91+
inorder_root = inorder_indices[val] # O(1)
92+
return TreeNode(
93+
val,
94+
left=build(inorder_s, inorder_root),
95+
right=build(inorder_root + 1, inorder_e),
96+
)
97+
98+
return build(0, len(inorder))

counting-bits/haklee.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""TC: O(n), SC: O(n)
2+
3+
아이디어:
4+
- bin으로 변환한 값의 길이가 k인 모든 수들에 대한 bit_count값을 알고 있다고 하자.
5+
- 사실 위의 수들은 bin으로 변환했을때 맨 앞 자리가 0으로 시작하는 길이 k+1의 수라고 볼 수 있다.
6+
- 그렇다면 bin으로 변환했을때 맨 앞 자리가 1로 시작하는 길이 k+1인 수들의 bit_count는
7+
맨 앞 자리가 0으로 시작하는 수들의 bit_count에 1을 더한 것이라고 할 수 있다.
8+
- 위의 아이디어를 활용하면 앞 2^k 수들의 bit_count를 알고 있으면 간단한 더하기 연산을 통해
9+
이후 2^k 수들의 bit_count값도 알 수 있다.
10+
e.g.)
11+
- 0, 1의 bit_count가 [0, 1]이라면, 2, 3의 bit_count는 [0+1, 1+1], 즉, 0~3의 bit_count는 [0, 1, 1, 2]
12+
- 0~3의 bit_count가 [0, 1, 1, 2]라면, 4~7의 bit_count는 [1, 2, 2, 3], 즉, 0~7의 bit_count는
13+
[0, 1, 1, 2, 1, 2, 2, 3]
14+
- ...
15+
- 리스트의 크기를 2배씩 늘리다가 n보다 커졌을때 앞 n개의 아이템만 취해서 리턴.
16+
17+
18+
SC:
19+
- 아래에서 리스트 s의 길이는 2^(k-1) < n <= 2^k를 만족하는 2^k만큼 커진다.
20+
- 즉, O(n).
21+
22+
TC:
23+
- s 안에 들어있는 i번째 아이템을 계산할때 필요한 연산은 덧셈 1회, 즉, O(1).
24+
- i번째 아이템 값을 구하기 위해 그 앞의 값을 미리 계산해둔 것이라 생각할 수 있다.
25+
- SC 분석과 비슷하게, 2^(k-1) < n <= 2^k를 만족하는 2^k만큼 반복. 즉, O(n).
26+
"""
27+
28+
29+
class Solution:
30+
def countBits(self, n: int) -> List[int]:
31+
s = [0]
32+
m = n * 2
33+
while m := m >> 1:
34+
s += [i + 1 for i in s]
35+
return s[: n + 1]

decode-ways/haklee.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""TC: O(n), SC: O(1)
2+
3+
아이디어:
4+
뒷 k개의 글자를 디코딩 하는 경우의 수를 f(k)라고 하자.
5+
f(k)는 다음의 두 경우의 수를 더한 값이다.
6+
- 뒷 k개의 글자 중 첫 글자가 디코딩 가능한 경우, 뒷 k-1글자를 디코딩하는 경우의 수
7+
- 뒷 k개의 글자 중 앞 두 글자가 디코딩 가능한 경우, 뒷 k-2글자를 디코딩하는 경우의 수
8+
즉, f(k) = (앞 두 글자 판별)*f(k-2) + (앞 한 글자 판별)*f(k-1)
9+
10+
11+
SC:
12+
- tabulation 과정에서 값 2개만 계속 유지한다.
13+
- 즉, O(1).
14+
15+
TC:
16+
- f(k) 구하는 식: O(1)
17+
- 두 글자가 디코딩 가능한지 판별: O(1)
18+
- 첫 글자가 디코딩 가능한지 판별: O(1)
19+
- 위의 f(k)를 구하는 것을 s의 길이에서 2를 뺀 수만큼 루프, 즉, O(n)
20+
- 종합하면 O(n).
21+
"""
22+
23+
24+
class Solution:
25+
def numDecodings(self, s: str) -> int:
26+
# init
27+
x, y = 1, int(int(s[-1]) != 0) # f(0), f(1)
28+
# tabulation
29+
for i in range(len(s) - 2, -1, -1): # 뒷 k개 글자의 시작 글자가 s[i]
30+
# f(k-2), f(k-1)을 f(k-1), f(k)로
31+
x, y = y, (x * (10 <= int(s[i : i + 2]) <= 26)) + (y * (int(s[i]) != 0))
32+
return y

encode-and-decode-strings/haklee.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
encode: TC: O(n), SC: O(l)
3+
decode: TC: O(n), SC: O(l)
4+
5+
input으로 들어온 string들의 길이를 전부 더한 값을 l이라고 하자.
6+
input에 들어있는 아이템 개수를 n이라고 하자.
7+
8+
아이디어:
9+
- 인풋을 처리한 값 앞쪽에 인풋을 해석하기 위한 정보를 두자.
10+
- 즉, 인풋을 처리한 값을 일종의 body로 보고 앞에 header를 붙이는 접근.
11+
- header는 body의 값이 어떻게 들어와도 영향을 받지 않는다!
12+
- 다음과 같이 encode를 한다.
13+
- encode한 string은 f'{header}:{body}'형식으로 되어있다.
14+
- body에는 input에 있는 값을 바로 concat한 값을 쓴다.
15+
- header에는 input에 있는 string의 길이를 ','로 구분한 값을 쓴다.
16+
- e.g.)
17+
body: ['a', 'bc', 'd'] -> 'abcd'
18+
header: ['a', 'bc', 'd'] -> '1,2,1'
19+
encoded string: '1,2,1:abcd'
20+
- 다음과 같이 decode를 한다.
21+
- 첫 번째로 등장하는 ':'을 찾아서 split한다.
22+
- 앞 부분이 header, 뒷 부분이 body다.
23+
- header를 다음과 같이 처리한다.
24+
- ','로 split해서 `x: List[int]`를 얻는다.
25+
- body를 다음과 같이 처리한다.
26+
- body를 앞서 구한 x에 들어있는 길이로 쭉 쪼개면 된다.
27+
- x의 누적합을 구하면서 이를 시작 인덱스로 활용한다.
28+
29+
SC:
30+
- encode
31+
- body의 길이는 l이다. 즉, O(l).
32+
- header의 길이는 최악의 경우 O(l)이다.
33+
- input의 모든 글자가 길이 1일때, 최악의 경우 O(l).
34+
- input의 모든 글자가 한 단어일때, 최고의 경우 O(log l).
35+
- 종합하면 O(l) + O(l)이라 O(l)이다.
36+
- decode
37+
- 전체 메시지의 body에 들어있는 값을 쪼개서 리스트로 만드는 것이므로 O(l).
38+
- 길이 값을 split해서 리스트로 만든다. O(n)
39+
- 종합하면 O(l + n)인데, 이때 n은 무조건 l 이하이므로 O(l).
40+
41+
TC:
42+
- encode
43+
- n개의 아이템을 순회하면서 길이를 얻는다. O(n).
44+
- decode
45+
- 길이 값을 split해서 리스트로 만든다. O(n).
46+
- 누적합을 활용하여 길이 값 리스트를 한 번 순회. O(n).
47+
- 종합하면 O(n).
48+
"""
49+
50+
51+
class Solution:
52+
"""
53+
@param: strs: a list of strings
54+
@return: encodes a list of strings to a single string.
55+
"""
56+
57+
def encode(self, strs):
58+
body = "".join(strs)
59+
header = ",".join([str(len(i)) for i in strs])
60+
return header + ":" + body
61+
62+
"""
63+
@param: str: A string
64+
@return: decodes a single string to a list of strings
65+
"""
66+
67+
def decode(self, str):
68+
header, body = str.split(":", 1)
69+
len_list = [int(i) for i in header.split(",")]
70+
start_ind = 0
71+
result = []
72+
for i in len_list:
73+
result.append(body[start_ind : start_ind + i])
74+
start_ind += i
75+
return result

valid-anagram/haklee.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""TC: O(n), SC: O(n)
2+
3+
여기서 n은 s, t의 길이값 중 큰 것이라 가정.
4+
5+
SC:
6+
- Counter는 s, t에 들어있는 글자들을 key로 하는 dict. 즉, SC는 O(n).
7+
8+
TC:
9+
- s, t에 들어있는 글자들을 key로 dict를 업데이트를 한 번 할때 O(1).
10+
- 위의 과정을 길이 n만큼 반복하므로 O(n).
11+
"""
12+
13+
from collections import Counter
14+
15+
16+
class Solution:
17+
def isAnagram(self, s: str, t: str) -> bool:
18+
# return sorted(s) == sorted(t) # TC: O(n log n), SC: O(n)
19+
return Counter(s) == Counter(t) # TC: O(n), SC: O(n)

0 commit comments

Comments
 (0)