diff --git a/combination-sum/seungriyou.py b/combination-sum/seungriyou.py new file mode 100644 index 000000000..467f1379a --- /dev/null +++ b/combination-sum/seungriyou.py @@ -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 diff --git a/decode-ways/seungriyou.py b/decode-ways/seungriyou.py new file mode 100644 index 000000000..a47d3d5ec --- /dev/null +++ b/decode-ways/seungriyou.py @@ -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 diff --git a/maximum-subarray/seungriyou.py b/maximum-subarray/seungriyou.py new file mode 100644 index 000000000..92e65a5aa --- /dev/null +++ b/maximum-subarray/seungriyou.py @@ -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 diff --git a/number-of-1-bits/seungriyou.py b/number-of-1-bits/seungriyou.py new file mode 100644 index 000000000..26e230b8b --- /dev/null +++ b/number-of-1-bits/seungriyou.py @@ -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 diff --git a/valid-palindrome/seungriyou.py b/valid-palindrome/seungriyou.py new file mode 100644 index 000000000..b8a374d9e --- /dev/null +++ b/valid-palindrome/seungriyou.py @@ -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)