Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SunaDu] Week 2 #731

Merged
merged 8 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions 3sum/dusunax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'''
# Leetcode 15. 3Sum

use **two pointers** to solve this problem.

## Time and Space Complexity

```
TC: O(n^2)
SC: O(1)
```

### TC is O(n^2):
- sorting the list = O(n log n)
- iterating through the list and using two pointers to find the sum of three numbers. = O(n^2)

### SC is O(1):
- sorting in place = O(1)
'''

class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() # TC: O(n log n), SC: O(1)
result = [] # result are part of the output => do not count toward auxiliary (extra) space.

for i in range(len(nums)): # TC: O(n^2)
if i > 0 and nums[i] == nums[i - 1]:
continue

j = i + 1
k = len(nums) - 1
while j < k:
currSum = nums[i] + nums[j] + nums[k]

if currSum < 0:
j += 1
elif currSum > 0:
k -= 1
else:
result.append([nums[i], nums[j], nums[k]])

while j < k and nums[j] == nums[j + 1]:
j += 1
while j < k and nums[k] == nums[k - 1]:
k -= 1

j += 1
k -= 1

return result
71 changes: 71 additions & 0 deletions climbing-stairs/dusunax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'''
# Leetcode 70. Climbing Stairs

use `dynamic programming` to solve the problem.

1. Bottom-up approach
2. Top-down approach

## Time and Space Complexity

### 1. Bottom-up approach

```
TC: O(n)
SC: O(1)
```

#### TC is O(n):
- iterating with a for loop. O(n)

#### SC is O(1):
- using a constant space to store the previous two steps. O(1)

### 2. Top-down approach

```
TC: O(n)
SC: O(n)
```

#### TC is O(n):
- performing a recursive call for each step. O(n)

#### SC is O(n):
- using a memoization object to store the previous two steps. O(n)
'''

class Solution:
'''
1. Bottom-up approach
'''
def climbStairsLoop(self, n: int) -> int:
if n == 1 or n == 2:
return n

# SC: O(1)
prev2 = 1 # ways to step 0
prev1 = 2 # ways to step 1

for i in range(3, n + 1): # TC: O(n)
current = prev1 + prev2 # ways to (n-1) + (n-2)
prev2 = prev1
prev1 = current

return prev1

'''
2. Top-down approach
'''
def climbStairsRecursive(self, n: int) -> int:
memo = {} # SC: O(n)

def dp(step: int, memo: int) -> int: # TC: O(n)
if step == 1 or step == 2:
memo[step] = step
if step not in memo:
memo[step] = dp(step - 1, memo) + dp(step - 2, memo) # ways to (n-1) + (n-2)
return memo[step]

return dp(n, memo)

119 changes: 119 additions & 0 deletions construct-binary-tree-from-preorder-and-inorder-traversal/dusunax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@

'''
# Leetcode 105. Construct Binary Tree from Preorder and Inorder Traversal

use **recursive** to solve this problem.

## Time and Space Complexity

### A. recursive & change range of preorder and inorder

```
TC: O(n)
SC: O(n)
```

### B. recursive & search index (of inorder)

```
TC: O(n^2)
SC: O(n)
```

### C. recursive & hash table

```
TC: O(n)
SC: O(n)
```

'''
class Solution:
'''
A. 재귀 풀이
preorder와 inorder의 각각의 범위를 조정하여 트리를 생성
'''
def buildTreeA(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
def setTree(pre_left, pre_right, in_left, in_right):
# 재귀 종료 조건: preorder 범위가 유효하지 않은 경우
if pre_left > pre_right:
return None

val = preorder[pre_left] # preorder의 현재 루트 노드 값 가져오기
mid = TreeNode(val) # 루트 노드를 먼저 생성

mid_inorder = inorder_idx_map[val] # 루트 노드의 inorder 인덱스 가져오기
left_size = mid_inorder - in_left # 왼쪽 서브트리의 크기 계산

# 왼쪽 서브트리 생성: preorder와 inorder의 범위를 왼쪽 서브트리로 조정
mid.left = setTree(
pre_left + 1, pre_left + left_size, in_left, mid_inorder - 1
)

# 오른쪽 서브트리 생성: preorder와 inorder의 범위를 오른쪽 서브트리로 조정
mid.right = setTree(
pre_left + left_size + 1, pre_right, mid_inorder + 1, in_right
)

return mid # 현재 노드 반환

# inorder를 값 -> 인덱스 맵핑한 딕셔너리 생성 - TC: O(n), SC: O(n)
inorder_idx_map = {value: idx for idx, value in enumerate(inorder)}

# 트리 생성 시작 (preorder와 inorder 전체 범위 사용) - TC: O(n), SC: O(n)
return setTree(0, len(preorder) - 1, 0, len(inorder) - 1)


'''
# B. 재귀 풀이 + 공간 최적화
# 레퍼런스 링크의 풀이 2: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
# 특징: 순회 시마다 인덱스를 찾는 과정이 있음
'''
def buildTreeB(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# pre: 현재 preorder에서 확인할 인덱스
# start, end: inorder에서 사용할 시작/종료 범위
def setTree(pre, start, end):
# 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우
if not (pre < len(preorder) and start <= end): # preorder에서 확인할 인덱스가 범위에서 나감, 투 포인터가 만남
return None

val = preorder[pre] # 현재 노드의 값
root = inorder.index(val) # 트리/서브트리의 루트 노드 인덱스 찾기 - TC: O(n)

left = setTree(pre + 1, start, root - 1)
# inorder에서 root노드의 왼쪽은 왼쪽 서브트리
# pre의 변화: 왼쪽 서브트리의 루트 노드를 찾기 위해 +1 이동

right = setTree(pre + 1 + root - start, root + 1, end)
# inorder에서 root노드의 오른쪽은 오른쪽 서브트리
# pre의 변화: 오른쪽 서브트리의 루트 노드를 찾기 위해 +1 이동 + (root - start) 👈 왼쪽 서브트리의 크기 만큼 더 이동

return TreeNode(preorder[pre], left, right) # 트리 노드 생성

# preorder 최초 인덱스 = 루트 노드(0), inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스
return setTree(0, 0, len(inorder) - 1) # TC: O(n^2), SC: O(n)

'''
C. 재귀 풀이 + 시간 최적화
레퍼런스 링크의 풀이 3: https://www.algodale.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
특징: A에서 preorder를 찾는 O(n) 과정을 해시 테이블을 사용하여 O(1)로 최적화
'''
def buildTreeC(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
# enumerate: 인덱스와 값을 동시에 반환
# inorder를 val -> idx로 매핑한 딕셔너리 생성
inorder_index_map = {val: idx for idx, val in enumerate(inorder)}
# preorder를 순회하기 위한 iterator 객체 생성
pre_iter = iter(preorder)

def setTree(start, end):
if start > end: # 재귀 종료 조건: 범위가 잘못되었거나 트리를 더 이상 만들 필요가 없는 경우
return None

root_val = next(pre_iter) # 현재 노드의 값, 매 순회마다 다음 preorder 노드(root)의 값을 가져옴
root = inorder_index_map[root_val] # 트리/서브트리의 루트 노드 인덱스를 O(1) 시간으로 찾기

left = setTree(start, root - 1) # 왼쪽 서브트리
right = setTree(root + 1, end) # 오른쪽 서브트리
return TreeNode(root_val, left, right) # 트리 노드 생성

return setTree(0, len(inorder) - 1) # inorder의 처음(0)과 끝(len(inorder) - 1) 인덱스
56 changes: 56 additions & 0 deletions decode-ways/dusunax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'''
# Leetcode 91. Decode Ways

use **dynamic programming** to solve this problem.

## Time and Space Complexity

```
TC: O(n)
SC: O(n)
```

### TC is O(n):
- iterating through the string and checking if the current character is decodable. = O(n)

### SC is O(n):
- creating a dp array of size n + 1 = O(n)
'''
class Solution:
def isDecodable(self, str: str):
return 1 <= int(str) <= 26 and str[0] != '0'

def numDecodings(self, s: str) -> int:
if s[0] == "0":
return 0

n = len(s)
dp = (n + 1) * [0]
dp[0] = 1
dp[1] = 1

for i in range(2, n + 1):
one = s[i - 1]
two = s[i - 2:i]

if self.isDecodable(one):
dp[i] += dp[i - 1]
if self.isDecodable(two):
dp[i] += dp[i - 2]

return dp[n]

'''
# sudo code
- 헬퍼함수: 0으로 시작하지 않고, 1~26인 경우 True
- numDecodings함수
1. n: 문자열 s의 길이
2. dp: 결과를 저장할 배열, n+1
3. BaseCase: dp[0] = 1, dp[1] = 1
4. for loop 2 to n:
one = s의 i-1 위치의 1글자 (현재 글자)
two = s의 i-2부터 i까지 자른 2글자 (현재 글자 포함 이전 글자)
if one is decodable => dp[i] += dp[i - 1] i길이일 때, dp의 -1 경우의 만큼수 추가 (현재 글자를 한 글자로 해석)
if two is decodable => dp[i] += dp[i - 2] i길이일 때, dp의 -2 경우의 수 만큼 추가 (현재 글자를 두 글자로 해석)
5. dp[n] 반환: 최종 디코드 가능한 경우의 수 결과
'''
44 changes: 44 additions & 0 deletions valid-anagram/dusunax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'''
# Leetcode 242. Valid Anagram

use `Counter` to (1)compare the frequency of characters in both strings, and (2)try to compare the frequency more efficiently. 🔍

## Time and Space Complexity

```
TC: O(n)
SC: O(n)
```

### A. use frequency object

#### TC is O(n):
- iterating through the strings just once to compare the frequency of characters. O(n)

#### SC is O(n):
- creating a new string `converted_s` to store the

### B. use Counter more efficiently

#### TC is O(n):
- iterating through the strings just once to compare the frequency of characters. O(n)

#### SC is O(n):
- creating a new string `converted_s` to store the
'''
class Solution:
def isAnagramA(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False

frequency = Counter(s) # SC: O(n)

for char in t: # TC: O(n)
if char not in frequency or frequency[char] == 0: # TC: O(1)
return False
frequency[char] -= 1

return True

def isAnagramB(self, s: str, t: str) -> bool:
return Counter(s) == Counter(t) # TC: O(n), SC: O(n)
Loading