Skip to content

[seungriyou] Week 03 Solutions #1276

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

Merged
merged 5 commits into from
Apr 19, 2025
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
79 changes: 79 additions & 0 deletions combination-sum/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# https://leetcode.com/problems/combination-sum/

from typing import List

class Solution:
def combinationSum1(self, candidates: List[int], target: int) -> List[List[int]]:
"""
[Complexity]
- TC: O(n^{target/min(candidates)})
- 재귀 호출 트리의 최대 height는 target/min(candidates) (중복 가능하므로)
- 각 step에서 최대 n회 재귀 호출 (for i in range(idx, n))
- SC: O(target/min(candidates)) (* res 제외)
- recursion stack = 최대 O(target/min(candidates))
- combi = 최대 O(target/min(candidates))

[Approach]
backtracking(idx = 현재 보고있는 원소의 인덱스, tot_sum = 현재까지의 합)으로 접근한다.
- base condition: tot_sum이 target과 같으면 res에 추가하고, target 보다 크면 종료
- recursion: 현재 보고있는 원소의 인덱스의 이후에 있는 원소들을 backtracking으로 검사
(* "same number may be chosen from candidates" 이므로!)
"""
n = len(candidates)
combi = []
res = []

def backtracking(idx, tot_sum):
# base condition
if tot_sum == target:
res.append(combi[:])
return
if tot_sum > target:
return

# recur
for i in range(idx, n):
c = candidates[i]
combi.append(c)
backtracking(i, tot_sum + c)
combi.pop()

backtracking(0, 0)

return res

def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
"""
[Complexity]
- TC: O(n^{target/min(candidates)})
- SC: O(target/min(candidates)) (* res 제외)

[Approach]
기존 backtracking 풀이에 정렬을 추가해서 조금 더 최적화할 수 있다.
즉, candidates를 정렬한 후, 매 단계에서 candidates[idx] > target - tot_sum 일 때도 종료한다.
이론적으로 복잡도는 동일하나(TC의 경우, 정렬에 드는 O(nlogn) 보다 백트래킹에 드는 O(n^m)이 더 지배적), early stop이 가능해진다.
"""
n = len(candidates)
combi = []
res = []

candidates.sort() # -- 정렬

def backtracking(idx, tot_sum):
# base condition
if tot_sum == target:
res.append(combi[:])
return
if tot_sum > target or candidates[idx] > target - tot_sum: # -- optimize
return

# recur
for i in range(idx, n):
c = candidates[i]
combi.append(c)
backtracking(i, tot_sum + c)
combi.pop()

backtracking(0, 0)

return res
73 changes: 73 additions & 0 deletions decode-ways/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# https://leetcode.com/problems/decode-ways/

class Solution:
def numDecodings1(self, s: str) -> int:
"""
[Complexity]
- TC: O(n)
- SC: O(n)

[Approach]
DP로 풀 수 있다.
dp[i] = s[i]까지 봤을 때, 가능한 decoding 개수의 총합
1) s[i] 한 자리만 가능한 경우: s[i] != 0
=> dp[i] += dp[i - 1]
2) s[i - 1:i + 1] 두 자리 모두 가능한 경우: 10 <= s[i - 1:i + 1] <= 26
=> dp[i] += dp[i - 2]
따라서, 초기 값으로 dp[0], dp[1]을 먼저 채워주어야 한다.
"""

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

# early stop
if s[0] == "0":
return 0
if n == 1:
return 1

# initialize (dp[0], dp[1])
dp[0] = 1
if s[1] != "0":
dp[1] += dp[0]
if 10 <= int(s[0:2]) <= 26:
dp[1] += 1

# iterate (dp[2] ~)
for i in range(2, n):
# 1) s[i] 한 자리만 가능한 경우
if s[i] != "0":
dp[i] += dp[i - 1]
# 2) s[i - 1:i + 1] 두 자리 모두 가능한 경우
if 10 <= int(s[i - 1:i + 1]) <= 26:
dp[i] += dp[i - 2]

return dp[-1]

def numDecodings(self, s: str) -> int:
"""
[Complexity]
- TC: O(n)
- SC: O(1)

[Approach]
O(n) space DP에서 매 단계에서 dp[i - 1], dp[i - 2] 두 값만 확인하므로, O(1) space로 space optimize 할 수 있다.
dp[i - 1] = prev1
dp[i - 2] = prev2
"""

prev2, prev1 = 1, 1 if s[0] != "0" else 0

for i in range(1, len(s)):
curr = 0 # = dp[i]

# 1) s[i] 한 자리만 가능한 경우
if s[i] != "0":
curr += prev1
# 2) s[i - 1:i + 1] 두 자리 모두 가능한 경우
if 10 <= int(s[i - 1:i + 1]) <= 26:
curr += prev2

prev2, prev1 = prev1, curr

return prev1
44 changes: 44 additions & 0 deletions maximum-subarray/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# https://leetcode.com/problems/maximum-subarray/

from typing import List

class Solution:
def maxSubArray1(self, nums: List[int]) -> int:
"""
[Complexity]
- TC: O(n)
- SC: O(n)

[Approach]
dp[i] = nums[i]까지 봤을 때, (1) nums[i]가 포함되면서 (2) 가장 sum이 큰 subarray의 sum 값
= max(dp[i - 1] + num, num)
"""

n = len(nums)
dp = [0] * n
dp[0] = nums[0]
max_sum = nums[0]

for i in range(1, n):
dp[i] = max(dp[i - 1] + nums[i], nums[i])
max_sum = max(max_sum, dp[i])

return max_sum

def maxSubArray(self, nums: List[int]) -> int:
"""
[Complexity]
- TC: O(n)
- SC: O(1)

[Approach]
space optimized DP
"""

prev = max_sum = nums[0]

for i in range(1, len(nums)):
prev = max(prev + nums[i], nums[i])
max_sum = max(max_sum, prev)

return max_sum
52 changes: 52 additions & 0 deletions number-of-1-bits/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# https://leetcode.com/problems/number-of-1-bits/

class Solution:
def hammingWeight1(self, n: int) -> int:
"""
[Complexity]
- TC: O(logn) (어차피 O(1))
- SC: O(1)

[Approach]
Bit manipulation을 이용하여 n이 0이 될 때까지 다음을 반복한다.
1) 맨 오른쪽의 bit가 1이면 res에 1을 더하고, 0이면 0을 더한다.
2) n을 오른쪽으로 1 shift 한다.
"""
res = 0
while n:
res += n & 1
n >>= 1
return res

def hammingWeight2(self, n: int) -> int:
"""
[Complexity]
- TC: O(k) (k = number of set bits)
- SC: O(1)

[Approach] Brian Kernighan's Algorithm
오른쪽에서부터 1인 bit를 하나씩 지워가며 개수를 세면 된다.
이때, 오른쪽에서부터 1인 bit를 지우기 위해서는 n & n - 1 을 하면 된다.
(ref: https://leetcode.com/problems/number-of-1-bits/solutions/4341511/faster-lesser-3-methods-simple-count-brian-kernighan-s-algorithm-bit-manipulation-explained)
"""
res = 0
while n:
n &= n - 1
res += 1
return res

def hammingWeight(self, n: int) -> int:
"""
[Complexity]
- TC: O(logn) (어차피 O(1))
- SC: O(1)

[Approach]
n을 2로 나누면서 그 나머지 값이 1이면 res에 1을 더하고, 0이면 0을 더한다.
"""
res = 0
while n:
d, m = divmod(n, 2)
res += m
n = d
return res
56 changes: 56 additions & 0 deletions valid-palindrome/seungriyou.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# https://leetcode.com/problems/valid-palindrome/

class Solution:
def isPalindrome(self, s: str) -> bool:
"""
[Complexity]
- TC: O(n)
- SC: O(n)

[Approach]
1. non-alphanumeric 문자만 lowercase로 변환 후 리스트에 모은다.
2. 1번의 결과를 뒤집어도 동일한지 확인한다.
"""

s = [_s.lower() for _s in s if _s.isalnum()]

return s == s[::-1]

def isPalindrome1(self, s: str) -> bool:
"""
[Complexity]
- TC: O(n)
- SC: O(n)

[Approach]
1. lowercase로 변환 후 non-alphanumeric을 제거한다.
2. 1번의 결과를 뒤집어도 동일한지 확인한다.
"""
import re

_s = re.sub("[^a-z0-9]", "", s.lower())

return _s == _s[::-1]

def isPalindrome2(self, s: str) -> bool:
"""
[Complexity]
- TC: O(n)
- SC: O(n)

[Approach]
1. lowercase로 변환 후 non-alphanumeric을 제거한다.
2. 1번의 결과를 절반까지만 순회하며, 양끝에서부터 문자가 동일한지 확인한다.
"""
import re

_s = re.sub("[^a-z0-9]", "", s.lower())

def is_palindrome(string):
# 문자열의 절반까지만 순회하며, 양끝에서부터 문자가 동일한지 확인
for i in range((n := len(string)) // 2):
if string[i] != string[n - i - 1]:
return False
return True

return is_palindrome(_s)