Skip to content

Commit b98effe

Browse files
authored
Merge pull request #735 from thispath98/main
[thispath98] Week 2
2 parents 8e95fdc + 03ef4b9 commit b98effe

File tree

5 files changed

+307
-0
lines changed

5 files changed

+307
-0
lines changed

3sum/thispath98.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
class Solution:
2+
def threeSumSet(self, nums: List[int]) -> List[List[int]]:
3+
"""
4+
Intuition:
5+
두 값을 더하고, 세트 안에 값에서 0을 만족시키는 값이 있을 경우
6+
정답에 추가한다.
7+
세트(해시)의 접근이 O(1) 임을 이용한다.
8+
9+
Time Complexity:
10+
O(N^2):
11+
2중 for문이 있으므로, O(N^2)이다.
12+
for문 내부에 시간복잡도에 영향을 줄만한 코드는 없으며
13+
정렬은 O(3 log 3)이므로 무시 가능하다.
14+
15+
Space Complexity:
16+
O(N):
17+
중복된 값이 없다면 seen 세트는 N - 1개의 값을 저장한다.
18+
19+
Key takeaway:
20+
해시를 이용한 풀이는 잘 못풀어서 조금 더 연습해야겠다.
21+
또한, 리스트를 해시하기 위해 tuple로 변환하는 것에 대해서 처음 알았다.
22+
"""
23+
answer = set()
24+
for i in range(len(nums) - 2):
25+
seen = set()
26+
for j in range(i + 1, len(nums)):
27+
complement = -(nums[i] + nums[j])
28+
if complement in seen:
29+
answer.add(tuple(sorted([nums[i], nums[j], complement])))
30+
seen.add(nums[j])
31+
32+
return list(answer)
33+
34+
35+
class Solution:
36+
def threeSumTwoPointer(self, nums: List[int]) -> List[List[int]]:
37+
"""
38+
Intuition:
39+
i를 for문으로 증가시키면서, 매 iteration마다 two pointer를 사용한다.
40+
41+
Time Complexity:
42+
O(N^2):
43+
2중 for문이 있으므로, O(N^2)이다.
44+
for문 내부에 시간복잡도에 영향을 줄만한 코드는 없으며
45+
정렬은 O(3 log 3)이므로 무시 가능하다.
46+
47+
Space Complexity:
48+
O(1):
49+
포인터 3개만을 사용하므로, 공간 복잡도는 O(1)이다.
50+
51+
Key takeaway:
52+
투포인터를 응용한 문제임을 떠올리긴 했으나,
53+
nested two pointer임을 인지하지 못했다.
54+
이러한 경우에도 더 고민을 해봐야겠다.
55+
"""
56+
nums.sort()
57+
58+
answer = set()
59+
for i in range(len(nums) - 2):
60+
# 만약 i가 이전의 값과 중복된 값이라면 이 작업은 필요 없다.
61+
if i > 0 and nums[i] == nums[i - 1]:
62+
continue
63+
64+
j = i + 1
65+
k = len(nums) - 1
66+
while j < k:
67+
if nums[i] + nums[j] + nums[k] == 0:
68+
answer.add(tuple(sorted([nums[i], nums[j], nums[k]])))
69+
j += 1
70+
elif nums[i] + nums[j] + nums[k] > 0:
71+
k -= 1
72+
else:
73+
j += 1
74+
75+
return list(answer)

climbing-stairs/thispath98.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
class Solution:
2+
def climbStairsFact(self, n: int) -> int:
3+
"""
4+
Intuition:
5+
1 + 1 + ... + 1 는 2가 0개일 때 n을 만들 수 있는 경우의 수 = 1.
6+
1 + 2 + ... + 1 는 2가 1개일 때 n을 만들 수 있는 경우의 수 = (n-1)C1.
7+
2 + 2 + ... + 1 는 2가 2개일 때 n을 만들 수 있는 경우의 수 = (n-2)C2.
8+
...
9+
즉, n이 0부터 최대로 놓을 수 있는 값(two_cnt)까지
10+
1로 놓여져 있는 배열에서 2의 위치를 선택(조합)하는 것과 같다.
11+
12+
Time Complexity:
13+
O(N^2 log N):
14+
(n-i)Ci는 O((N - i) log i)이고, i가 0부터 two_cnt까지 증가할 경우
15+
대략 O(N log N)로 계산한다.
16+
이를 two_cnt(N//2) 까지 반복하므로, O(N^2 log N).
17+
18+
Space Complexity:
19+
O(1):
20+
answer를 업데이트 해가며 값 계산.
21+
"""
22+
import math
23+
24+
two_cnt = n // 2
25+
answer = 1
26+
27+
for i in range(1, two_cnt + 1):
28+
# (n - i)Ci
29+
# 여기서 int로 형변환 할 경우 수치적 불안정 발생
30+
answer += math.factorial(n - i) / math.factorial(n - 2 * i) / math.factorial(i)
31+
32+
return int(answer) # int로 형변환 하지 않을 경우 TypeError
33+
34+
def climbStairsComb(self, n: int) -> int:
35+
"""
36+
Intuition:
37+
`climbStairsFact`에서 Factorial은 수치적 불안정성으로 인해
38+
더욱 안정적인 math.comb를 사용한다.
39+
40+
Time Complexity:
41+
O(N^2 log N):
42+
(n-i)Ci는 O((N - i) log i)이고, i가 0부터 two_cnt까지 증가할 경우
43+
대략 O(N log N)로 계산한다.
44+
이를 two_cnt(N//2) 까지 반복하므로, O(N^2 log N).
45+
46+
Space Complexity:
47+
O(1):
48+
answer를 업데이트 해가며 값 계산.
49+
"""
50+
import math
51+
52+
two_cnt = n // 2
53+
answer = 1
54+
55+
for i in range(1, two_cnt + 1):
56+
# (n - i)Ci
57+
# math.comb 메소드는 수치적으로 안정적으로 계산해준다
58+
answer += math.comb(n - i, i)
59+
return answer
60+
61+
def climbStairsFib(self, n: int) -> int:
62+
"""
63+
Intuition:
64+
climb stairs 문제는 대표적인 피보나치 수열 문제이다.
65+
실제로 경우의 수를 계산해보면,
66+
1 -> 1
67+
2 -> 2
68+
3 -> 3
69+
4 -> 5
70+
5 -> 8
71+
...
72+
로 피보나치 수열을 따르는 것을 알 수 있다.
73+
74+
Time Complexity:
75+
O(N):
76+
N번 순회하여 피보나치 수열 구현.
77+
78+
Space Complexity:
79+
O(N):
80+
N개 만큼 해시 key-value 쌍을 저장.
81+
"""
82+
fib_dict = {1: 1, 2: 2, 3: 3}
83+
for i in range(1, n + 1):
84+
if i not in fib_dict:
85+
fib_dict[i] = fib_dict[i - 1] + fib_dict[i - 2]
86+
if i == n:
87+
return fib_dict[i]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Definition for a binary tree node.
2+
# class TreeNode:
3+
# def __init__(self, val=0, left=None, right=None):
4+
# self.val = val
5+
# self.left = left
6+
# self.right = right
7+
class Solution:
8+
def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
9+
"""
10+
Intuition:
11+
preorder 트리의 첫번째 원소는 항상 루트이다.
12+
또한, inorder 트리에서 루트를 기준으로 왼쪽은 left child,
13+
오른쪽은 right child를 의미한다.
14+
따라서 이를 이용해 재귀적으로 호출한다.
15+
16+
Time Complexity:
17+
O(N^2):
18+
parent_idx를 선택하는 데에 O(N)이 소요되고
19+
최악의 경우 N번 재귀 호출해야 하므로 O(N^2)이다.
20+
21+
Space Complexity:
22+
O(N):
23+
TreeNode는 N개의 값을 저장한다.
24+
25+
Key takeaway:
26+
리트코드에서 클래스를 반환하는 문제는 다음처럼 하는 것을
27+
처음 알게 되었다.
28+
"""
29+
if not preorder:
30+
return None
31+
32+
parent = preorder[0]
33+
parent_idx = inorder.index(parent) # O(N)
34+
35+
left_pre = preorder[1 :parent_idx + 1]
36+
left_in = inorder[:parent_idx]
37+
left = self.buildTree(left_pre, left_in)
38+
39+
right_pre = preorder[1 + parent_idx:]
40+
right_in = inorder[1 + parent_idx:]
41+
right = self.buildTree(right_pre, right_in)
42+
43+
tree = TreeNode(parent, left, right)
44+
45+
return tree

decode-ways/thispath98.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
class Solution:
2+
def numDecodings(self, s: str) -> int:
3+
"""
4+
Intuition:
5+
문자를 쪼개서 디코드 조합을 얻는다.
6+
이후, 디코드 조합에서 2개씩 묶을 경우 26보다 큰 수가 있을 수 있으므로
7+
다시 한번 쪼갠다.
8+
마지막으로 각 디코드 조합에서 값을 얻는 경우는
9+
피보나치 수열을 따른다.
10+
11+
Time Complexity:
12+
O(N):
13+
문자를 쪼개고, 묶는 문자 조합을 구하고,
14+
피보나치 수열에서 값을 찾는 것은 모두 O(N)만큼 소요된다.
15+
16+
Space Complexity:
17+
O(1):
18+
최악의 경우 N개에 대한 피보나치 수열을 구해야 하고,
19+
N은 최대 100이므로 O(1)이다.
20+
"""
21+
if s[0] == "0":
22+
return 0
23+
24+
# 문자열에서 0 앞에 1 혹은 2가 붙는지 확인
25+
# 그렇지 않다면, 디코드 할 수 없으므로 return 0
26+
# O(N)
27+
splitted_s = []
28+
start = 0
29+
for i in range(len(s)):
30+
if s[i] == "0":
31+
if s[i - 1] in "12":
32+
splitted_s.append(s[start: i - 1])
33+
start = i + 1
34+
else:
35+
return 0
36+
splitted_s.append(s[start:])
37+
38+
# 쪼개진 문자에서 두 문자를 보고, 묶을 수 있는지
39+
# (26 이하인지)를 확인한다.
40+
# 묶을 수 없다면, 문자를 다시 한번 쪼갠다.
41+
# O(N)
42+
interval = []
43+
for splitted in splitted_s:
44+
start = 0
45+
for i in range(1, len(splitted)):
46+
if int(splitted[i - 1: i + 1]) > 26:
47+
interval.append(i - start)
48+
start = i
49+
50+
interval.append(len(splitted) - start)
51+
52+
answer = 1
53+
fib_dict = {0: 1, 1: 1, 2: 2}
54+
55+
56+
def get_fib(n):
57+
if n not in fib_dict:
58+
fib_dict[n] = get_fib(n - 1) + get_fib(n - 2)
59+
return fib_dict[n]
60+
61+
62+
# 쪼개진 문자에서 디코드 조합은
63+
# 문자 개수를 피보나치 수열에 넣은 값이다.
64+
# 이 값들은 쪼개진 문자들에 대하여 곱셈으로 계산된다.
65+
# O(N)
66+
get_fib(max(interval))
67+
for n in interval:
68+
answer *= fib_dict[n]
69+
return answer

valid-anagram/thispath98.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
class Solution:
3+
def isAnagram(self, s: str, t: str) -> bool:
4+
"""
5+
Time Complexity:
6+
O(N log N):
7+
두 string을 정렬, 이는 보통 quick sort로 구현되어
8+
N log N 만큼 소요된다.
9+
10+
Space Complexity:
11+
O(N):
12+
최악의 경우 (모든 string이 유일할 경우) N개의 리스트
13+
를 저장한다.
14+
"""
15+
return sorted(s) == sorted(t)
16+
17+
def isAnagramCounter(self, s: str, t: str) -> bool:
18+
"""
19+
Time Complexity:
20+
O(N):
21+
해시를 기반으로 일치 여부 탐색, N개의 엔트리를
22+
한번씩 순회하는 것으로 구현된다.
23+
24+
Space Complexity:
25+
O(N):
26+
최악의 경우 (모든 string이 유일할 경우) N개의 리스트
27+
를 저장한다.
28+
"""
29+
from collections import Counter
30+
31+
return Counter(s) == Counter(t)

0 commit comments

Comments
 (0)