Skip to content

Commit c11f6cb

Browse files
authored
Merge pull request #731 from dusunax/main
[SunaDu] Week 2
2 parents e4bf20a + b506385 commit c11f6cb

File tree

5 files changed

+340
-0
lines changed

5 files changed

+340
-0
lines changed

3sum/dusunax.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'''
2+
# Leetcode 15. 3Sum
3+
4+
use **two pointers** to solve this problem.
5+
6+
## Time and Space Complexity
7+
8+
```
9+
TC: O(n^2)
10+
SC: O(1)
11+
```
12+
13+
### TC is O(n^2):
14+
- sorting the list = O(n log n)
15+
- iterating through the list and using two pointers to find the sum of three numbers. = O(n^2)
16+
17+
### SC is O(1):
18+
- sorting in place = O(1)
19+
'''
20+
21+
class Solution:
22+
def threeSum(self, nums: List[int]) -> List[List[int]]:
23+
nums.sort() # TC: O(n log n), SC: O(1)
24+
result = [] # result are part of the output => do not count toward auxiliary (extra) space.
25+
26+
for i in range(len(nums)): # TC: O(n^2)
27+
if i > 0 and nums[i] == nums[i - 1]:
28+
continue
29+
30+
j = i + 1
31+
k = len(nums) - 1
32+
while j < k:
33+
currSum = nums[i] + nums[j] + nums[k]
34+
35+
if currSum < 0:
36+
j += 1
37+
elif currSum > 0:
38+
k -= 1
39+
else:
40+
result.append([nums[i], nums[j], nums[k]])
41+
42+
while j < k and nums[j] == nums[j + 1]:
43+
j += 1
44+
while j < k and nums[k] == nums[k - 1]:
45+
k -= 1
46+
47+
j += 1
48+
k -= 1
49+
50+
return result

climbing-stairs/dusunax.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
'''
2+
# Leetcode 70. Climbing Stairs
3+
4+
use `dynamic programming` to solve the problem.
5+
6+
1. Bottom-up approach
7+
2. Top-down approach
8+
9+
## Time and Space Complexity
10+
11+
### 1. Bottom-up approach
12+
13+
```
14+
TC: O(n)
15+
SC: O(1)
16+
```
17+
18+
#### TC is O(n):
19+
- iterating with a for loop. O(n)
20+
21+
#### SC is O(1):
22+
- using a constant space to store the previous two steps. O(1)
23+
24+
### 2. Top-down approach
25+
26+
```
27+
TC: O(n)
28+
SC: O(n)
29+
```
30+
31+
#### TC is O(n):
32+
- performing a recursive call for each step. O(n)
33+
34+
#### SC is O(n):
35+
- using a memoization object to store the previous two steps. O(n)
36+
'''
37+
38+
class Solution:
39+
'''
40+
1. Bottom-up approach
41+
'''
42+
def climbStairsLoop(self, n: int) -> int:
43+
if n == 1 or n == 2:
44+
return n
45+
46+
# SC: O(1)
47+
prev2 = 1 # ways to step 0
48+
prev1 = 2 # ways to step 1
49+
50+
for i in range(3, n + 1): # TC: O(n)
51+
current = prev1 + prev2 # ways to (n-1) + (n-2)
52+
prev2 = prev1
53+
prev1 = current
54+
55+
return prev1
56+
57+
'''
58+
2. Top-down approach
59+
'''
60+
def climbStairsRecursive(self, n: int) -> int:
61+
memo = {} # SC: O(n)
62+
63+
def dp(step: int, memo: int) -> int: # TC: O(n)
64+
if step == 1 or step == 2:
65+
memo[step] = step
66+
if step not in memo:
67+
memo[step] = dp(step - 1, memo) + dp(step - 2, memo) # ways to (n-1) + (n-2)
68+
return memo[step]
69+
70+
return dp(n, memo)
71+
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
2+
'''
3+
# Leetcode 105. Construct Binary Tree from Preorder and Inorder Traversal
4+
5+
use **recursive** to solve this problem.
6+
7+
## Time and Space Complexity
8+
9+
### A. recursive & change range of preorder and inorder
10+
11+
```
12+
TC: O(n)
13+
SC: O(n)
14+
```
15+
16+
### B. recursive & search index (of inorder)
17+
18+
```
19+
TC: O(n^2)
20+
SC: O(n)
21+
```
22+
23+
### C. recursive & hash table
24+
25+
```
26+
TC: O(n)
27+
SC: O(n)
28+
```
29+
30+
'''
31+
class Solution:
32+
'''
33+
A. 재귀 풀이
34+
preorder와 inorder의 각각의 범위를 조정하여 트리를 생성
35+
'''
36+
def buildTreeA(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
37+
def setTree(pre_left, pre_right, in_left, in_right):
38+
# 재귀 종료 조건: preorder 범위가 유효하지 않은 경우
39+
if pre_left > pre_right:
40+
return None
41+
42+
val = preorder[pre_left] # preorder의 현재 루트 노드 값 가져오기
43+
mid = TreeNode(val) # 루트 노드를 먼저 생성
44+
45+
mid_inorder = inorder_idx_map[val] # 루트 노드의 inorder 인덱스 가져오기
46+
left_size = mid_inorder - in_left # 왼쪽 서브트리의 크기 계산
47+
48+
# 왼쪽 서브트리 생성: preorder와 inorder의 범위를 왼쪽 서브트리로 조정
49+
mid.left = setTree(
50+
pre_left + 1, pre_left + left_size, in_left, mid_inorder - 1
51+
)
52+
53+
# 오른쪽 서브트리 생성: preorder와 inorder의 범위를 오른쪽 서브트리로 조정
54+
mid.right = setTree(
55+
pre_left + left_size + 1, pre_right, mid_inorder + 1, in_right
56+
)
57+
58+
return mid # 현재 노드 반환
59+
60+
# inorder를 값 -> 인덱스 맵핑한 딕셔너리 생성 - TC: O(n), SC: O(n)
61+
inorder_idx_map = {value: idx for idx, value in enumerate(inorder)}
62+
63+
# 트리 생성 시작 (preorder와 inorder 전체 범위 사용) - TC: O(n), SC: O(n)
64+
return setTree(0, len(preorder) - 1, 0, len(inorder) - 1)
65+
66+
67+
'''
68+
# B. 재귀 풀이 + 공간 최적화
69+
# 레퍼런스 링크의 풀이 2: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
70+
# 특징: 순회 시마다 인덱스를 찾는 과정이 있음
71+
'''
72+
def buildTreeB(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
73+
# pre: 현재 preorder에서 확인할 인덱스
74+
# start, end: inorder에서 사용할 시작/종료 범위
75+
def setTree(pre, start, end):
76+
# 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우
77+
if not (pre < len(preorder) and start <= end): # preorder에서 확인할 인덱스가 범위에서 나감, 투 포인터가 만남
78+
return None
79+
80+
val = preorder[pre] # 현재 노드의 값
81+
root = inorder.index(val) # 트리/서브트리의 루트 노드 인덱스 찾기 - TC: O(n)
82+
83+
left = setTree(pre + 1, start, root - 1)
84+
# inorder에서 root노드의 왼쪽은 왼쪽 서브트리
85+
# pre의 변화: 왼쪽 서브트리의 루트 노드를 찾기 위해 +1 이동
86+
87+
right = setTree(pre + 1 + root - start, root + 1, end)
88+
# inorder에서 root노드의 오른쪽은 오른쪽 서브트리
89+
# pre의 변화: 오른쪽 서브트리의 루트 노드를 찾기 위해 +1 이동 + (root - start) 👈 왼쪽 서브트리의 크기 만큼 더 이동
90+
91+
return TreeNode(preorder[pre], left, right) # 트리 노드 생성
92+
93+
# preorder 최초 인덱스 = 루트 노드(0), inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스
94+
return setTree(0, 0, len(inorder) - 1) # TC: O(n^2), SC: O(n)
95+
96+
'''
97+
C. 재귀 풀이 + 시간 최적화
98+
레퍼런스 링크의 풀이 3: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
99+
특징: A에서 preorder를 찾는 O(n) 과정을 해시 테이블을 사용하여 O(1)로 최적화
100+
'''
101+
def buildTreeC(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
102+
# enumerate: 인덱스와 값을 동시에 반환
103+
# inorder를 val -> idx로 매핑한 딕셔너리 생성
104+
inorder_index_map = {val: idx for idx, val in enumerate(inorder)}
105+
# preorder를 순회하기 위한 iterator 객체 생성
106+
pre_iter = iter(preorder)
107+
108+
def setTree(start, end):
109+
if start > end: # 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우
110+
return None
111+
112+
root_val = next(pre_iter) # 현재 노드의 값, 매 순회마다 다음 preorder 노드(root)의 값을 가져옴
113+
root = inorder_index_map[root_val] # 트리/서브트리의 루트 노드 인덱스를 O(1) 시간으로 찾기
114+
115+
left = setTree(start, root - 1) # 왼쪽 서브트리
116+
right = setTree(root + 1, end) # 오른쪽 서브트리
117+
return TreeNode(root_val, left, right) # 트리 노드 생성
118+
119+
return setTree(0, len(inorder) - 1) # inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스

decode-ways/dusunax.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
'''
2+
# Leetcode 91. Decode Ways
3+
4+
use **dynamic programming** to solve this problem.
5+
6+
## Time and Space Complexity
7+
8+
```
9+
TC: O(n)
10+
SC: O(n)
11+
```
12+
13+
### TC is O(n):
14+
- iterating through the string and checking if the current character is decodable. = O(n)
15+
16+
### SC is O(n):
17+
- creating a dp array of size n + 1 = O(n)
18+
'''
19+
class Solution:
20+
def isDecodable(self, str: str):
21+
return 1 <= int(str) <= 26 and str[0] != '0'
22+
23+
def numDecodings(self, s: str) -> int:
24+
if s[0] == "0":
25+
return 0
26+
27+
n = len(s)
28+
dp = (n + 1) * [0]
29+
dp[0] = 1
30+
dp[1] = 1
31+
32+
for i in range(2, n + 1):
33+
one = s[i - 1]
34+
two = s[i - 2:i]
35+
36+
if self.isDecodable(one):
37+
dp[i] += dp[i - 1]
38+
if self.isDecodable(two):
39+
dp[i] += dp[i - 2]
40+
41+
return dp[n]
42+
43+
'''
44+
# sudo code
45+
- 헬퍼함수: 0으로 시작하지 않고, 1~26인 경우 True
46+
- numDecodings함수
47+
1. n: 문자열 s의 길이
48+
2. dp: 결과를 저장할 배열, n+1
49+
3. BaseCase: dp[0] = 1, dp[1] = 1
50+
4. for loop 2 to n:
51+
one = s의 i-1 위치의 1글자 (현재 글자)
52+
two = s의 i-2부터 i까지 자른 2글자 (현재 글자 포함 이전 글자)
53+
if one is decodable => dp[i] += dp[i - 1] i길이일 때, dp의 -1 경우의 만큼수 추가 (현재 글자를 한 글자로 해석)
54+
if two is decodable => dp[i] += dp[i - 2] i길이일 때, dp의 -2 경우의 수 만큼 추가 (현재 글자를 두 글자로 해석)
55+
5. dp[n] 반환: 최종 디코드 가능한 경우의 수 결과
56+
'''

valid-anagram/dusunax.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'''
2+
# Leetcode 242. Valid Anagram
3+
4+
use `Counter` to (1)compare the frequency of characters in both strings, and (2)try to compare the frequency more efficiently. 🔍
5+
6+
## Time and Space Complexity
7+
8+
```
9+
TC: O(n)
10+
SC: O(n)
11+
```
12+
13+
### A. use frequency object
14+
15+
#### TC is O(n):
16+
- iterating through the strings just once to compare the frequency of characters. O(n)
17+
18+
#### SC is O(n):
19+
- creating a new string `converted_s` to store the
20+
21+
### B. use Counter more efficiently
22+
23+
#### TC is O(n):
24+
- iterating through the strings just once to compare the frequency of characters. O(n)
25+
26+
#### SC is O(n):
27+
- creating a new string `converted_s` to store the
28+
'''
29+
class Solution:
30+
def isAnagramA(self, s: str, t: str) -> bool:
31+
if len(s) != len(t):
32+
return False
33+
34+
frequency = Counter(s) # SC: O(n)
35+
36+
for char in t: # TC: O(n)
37+
if char not in frequency or frequency[char] == 0: # TC: O(1)
38+
return False
39+
frequency[char] -= 1
40+
41+
return True
42+
43+
def isAnagramB(self, s: str, t: str) -> bool:
44+
return Counter(s) == Counter(t) # TC: O(n), SC: O(n)

0 commit comments

Comments
 (0)