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

[thispath98] Week 2 #735

Merged
merged 5 commits into from
Dec 22, 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
75 changes: 75 additions & 0 deletions 3sum/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
class Solution:
def threeSumSet(self, nums: List[int]) -> List[List[int]]:
"""
Intuition:
두 값을 더하고, 세트 안에 값에서 0을 만족시키는 값이 있을 경우
정답에 추가한다.
세트(해시)의 접근이 O(1) 임을 이용한다.

Time Complexity:
O(N^2):
2중 for문이 있으므로, O(N^2)이다.
for문 내부에 시간복잡도에 영향을 줄만한 코드는 없으며
정렬은 O(3 log 3)이므로 무시 가능하다.

Space Complexity:
O(N):
중복된 값이 없다면 seen 세트는 N - 1개의 값을 저장한다.

Key takeaway:
해시를 이용한 풀이는 잘 못풀어서 조금 더 연습해야겠다.
또한, 리스트를 해시하기 위해 tuple로 변환하는 것에 대해서 처음 알았다.
"""
answer = set()
for i in range(len(nums) - 2):
seen = set()
for j in range(i + 1, len(nums)):
complement = -(nums[i] + nums[j])
if complement in seen:
answer.add(tuple(sorted([nums[i], nums[j], complement])))
seen.add(nums[j])

return list(answer)


class Solution:
def threeSumTwoPointer(self, nums: List[int]) -> List[List[int]]:
"""
Intuition:
i를 for문으로 증가시키면서, 매 iteration마다 two pointer를 사용한다.

Time Complexity:
O(N^2):
2중 for문이 있으므로, O(N^2)이다.
for문 내부에 시간복잡도에 영향을 줄만한 코드는 없으며
정렬은 O(3 log 3)이므로 무시 가능하다.

Space Complexity:
O(1):
포인터 3개만을 사용하므로, 공간 복잡도는 O(1)이다.

Key takeaway:
투포인터를 응용한 문제임을 떠올리긴 했으나,
nested two pointer임을 인지하지 못했다.
이러한 경우에도 더 고민을 해봐야겠다.
"""
nums.sort()

answer = set()
for i in range(len(nums) - 2):
# 만약 i가 이전의 값과 중복된 값이라면 이 작업은 필요 없다.
if i > 0 and nums[i] == nums[i - 1]:
continue

j = i + 1
k = len(nums) - 1
while j < k:
if nums[i] + nums[j] + nums[k] == 0:
answer.add(tuple(sorted([nums[i], nums[j], nums[k]])))
j += 1
elif nums[i] + nums[j] + nums[k] > 0:
k -= 1
else:
j += 1

return list(answer)
87 changes: 87 additions & 0 deletions climbing-stairs/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
class Solution:
def climbStairsFact(self, n: int) -> int:
"""
Intuition:
1 + 1 + ... + 1 는 2가 0개일 때 n을 만들 수 있는 경우의 수 = 1.
1 + 2 + ... + 1 는 2가 1개일 때 n을 만들 수 있는 경우의 수 = (n-1)C1.
2 + 2 + ... + 1 는 2가 2개일 때 n을 만들 수 있는 경우의 수 = (n-2)C2.
...
즉, n이 0부터 최대로 놓을 수 있는 값(two_cnt)까지
1로 놓여져 있는 배열에서 2의 위치를 선택(조합)하는 것과 같다.

Time Complexity:
O(N^2 log N):
(n-i)Ci는 O((N - i) log i)이고, i가 0부터 two_cnt까지 증가할 경우
대략 O(N log N)로 계산한다.
이를 two_cnt(N//2) 까지 반복하므로, O(N^2 log N).

Space Complexity:
O(1):
answer를 업데이트 해가며 값 계산.
"""
import math

two_cnt = n // 2
answer = 1

for i in range(1, two_cnt + 1):
# (n - i)Ci
# 여기서 int로 형변환 할 경우 수치적 불안정 발생
answer += math.factorial(n - i) / math.factorial(n - 2 * i) / math.factorial(i)

return int(answer) # int로 형변환 하지 않을 경우 TypeError

def climbStairsComb(self, n: int) -> int:
"""
Intuition:
`climbStairsFact`에서 Factorial은 수치적 불안정성으로 인해
더욱 안정적인 math.comb를 사용한다.

Time Complexity:
O(N^2 log N):
(n-i)Ci는 O((N - i) log i)이고, i가 0부터 two_cnt까지 증가할 경우
대략 O(N log N)로 계산한다.
이를 two_cnt(N//2) 까지 반복하므로, O(N^2 log N).

Space Complexity:
O(1):
answer를 업데이트 해가며 값 계산.
"""
import math

two_cnt = n // 2
answer = 1

for i in range(1, two_cnt + 1):
# (n - i)Ci
# math.comb 메소드는 수치적으로 안정적으로 계산해준다
answer += math.comb(n - i, i)
return answer

def climbStairsFib(self, n: int) -> int:
"""
Intuition:
climb stairs 문제는 대표적인 피보나치 수열 문제이다.
실제로 경우의 수를 계산해보면,
1 -> 1
2 -> 2
3 -> 3
4 -> 5
5 -> 8
...
로 피보나치 수열을 따르는 것을 알 수 있다.

Time Complexity:
O(N):
N번 순회하여 피보나치 수열 구현.

Space Complexity:
O(N):
N개 만큼 해시 key-value 쌍을 저장.
"""
fib_dict = {1: 1, 2: 2, 3: 3}
for i in range(1, n + 1):
if i not in fib_dict:
fib_dict[i] = fib_dict[i - 1] + fib_dict[i - 2]
if i == n:
return fib_dict[i]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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]:
"""
Intuition:
preorder 트리의 첫번째 원소는 항상 루트이다.
또한, inorder 트리에서 루트를 기준으로 왼쪽은 left child,
오른쪽은 right child를 의미한다.
따라서 이를 이용해 재귀적으로 호출한다.

Time Complexity:
O(N^2):
parent_idx를 선택하는 데에 O(N)이 소요되고
최악의 경우 N번 재귀 호출해야 하므로 O(N^2)이다.

Space Complexity:
O(N):
TreeNode는 N개의 값을 저장한다.

Key takeaway:
리트코드에서 클래스를 반환하는 문제는 다음처럼 하는 것을
처음 알게 되었다.
"""
if not preorder:
return None

parent = preorder[0]
parent_idx = inorder.index(parent) # O(N)

left_pre = preorder[1 :parent_idx + 1]
left_in = inorder[:parent_idx]
left = self.buildTree(left_pre, left_in)

right_pre = preorder[1 + parent_idx:]
right_in = inorder[1 + parent_idx:]
right = self.buildTree(right_pre, right_in)

tree = TreeNode(parent, left, right)

return tree
69 changes: 69 additions & 0 deletions decode-ways/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
class Solution:
def numDecodings(self, s: str) -> int:
"""
Intuition:
문자를 쪼개서 디코드 조합을 얻는다.
이후, 디코드 조합에서 2개씩 묶을 경우 26보다 큰 수가 있을 수 있으므로
다시 한번 쪼갠다.
마지막으로 각 디코드 조합에서 값을 얻는 경우는
피보나치 수열을 따른다.

Time Complexity:
O(N):
문자를 쪼개고, 묶는 문자 조합을 구하고,
피보나치 수열에서 값을 찾는 것은 모두 O(N)만큼 소요된다.

Space Complexity:
O(1):
최악의 경우 N개에 대한 피보나치 수열을 구해야 하고,
N은 최대 100이므로 O(1)이다.
"""
if s[0] == "0":
return 0

# 문자열에서 0 앞에 1 혹은 2가 붙는지 확인
# 그렇지 않다면, 디코드 할 수 없으므로 return 0
# O(N)
splitted_s = []
start = 0
for i in range(len(s)):
if s[i] == "0":
if s[i - 1] in "12":
splitted_s.append(s[start: i - 1])
start = i + 1
else:
return 0
splitted_s.append(s[start:])

# 쪼개진 문자에서 두 문자를 보고, 묶을 수 있는지
# (26 이하인지)를 확인한다.
# 묶을 수 없다면, 문자를 다시 한번 쪼갠다.
# O(N)
interval = []
for splitted in splitted_s:
start = 0
for i in range(1, len(splitted)):
if int(splitted[i - 1: i + 1]) > 26:
interval.append(i - start)
start = i

interval.append(len(splitted) - start)

answer = 1
fib_dict = {0: 1, 1: 1, 2: 2}


def get_fib(n):
if n not in fib_dict:
fib_dict[n] = get_fib(n - 1) + get_fib(n - 2)
return fib_dict[n]


# 쪼개진 문자에서 디코드 조합은
# 문자 개수를 피보나치 수열에 넣은 값이다.
# 이 값들은 쪼개진 문자들에 대하여 곱셈으로 계산된다.
# O(N)
get_fib(max(interval))
for n in interval:
answer *= fib_dict[n]
return answer
31 changes: 31 additions & 0 deletions valid-anagram/thispath98.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

class Solution:
def isAnagram(self, s: str, t: str) -> bool:
"""
Time Complexity:
O(N log N):
두 string을 정렬, 이는 보통 quick sort로 구현되어
N log N 만큼 소요된다.

Space Complexity:
O(N):
최악의 경우 (모든 string이 유일할 경우) N개의 리스트
를 저장한다.
"""
return sorted(s) == sorted(t)

def isAnagramCounter(self, s: str, t: str) -> bool:
"""
Time Complexity:
O(N):
해시를 기반으로 일치 여부 탐색, N개의 엔트리를
한번씩 순회하는 것으로 구현된다.

Space Complexity:
O(N):
최악의 경우 (모든 string이 유일할 경우) N개의 리스트
를 저장한다.
"""
from collections import Counter

return Counter(s) == Counter(t)
Loading