diff --git a/combination-sum/hu6r1s.py b/combination-sum/hu6r1s.py new file mode 100644 index 000000000..6a96e6c76 --- /dev/null +++ b/combination-sum/hu6r1s.py @@ -0,0 +1,74 @@ +class Solution: + """ + 시간 복잡도 (Time Complexity): + - 이 문제는 백트래킹(DFS) 기반으로 모든 가능한 조합을 탐색합니다. + - 최악의 경우 각 조합에서 한 숫자를 여러 번 사용할 수 있으므로, 트리의 깊이는 최대 target // min(candidates) + - 각 깊이마다 최대 len(candidates)만큼 분기 가능 + - 따라서 시간 복잡도는 지수적으로 증가: O(2^T), T = target + (정확한 upper bound는 계산하기 어렵지만, 대략적으로는 O(2^T) 또는 O(k^T)로 볼 수 있음) + + 공간 복잡도 (Space Complexity): + - 재귀 호출의 최대 깊이: O(T), T = target (가장 작은 숫자만 반복해서 사용하는 경우) + - 경로 저장용 리스트(nums): O(T) + - 결과 저장용 리스트(output): 최악의 경우 모든 가능한 조합 저장 → O(number of valid combinations * 평균 길이) + 최종 공간 복잡도: **O(T + R)**, + T: 재귀 깊이 / R: 결과 조합 수가 클 경우 output이 차지하는 공간 + + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + output, nums = [], [] + + def dfs(start, total): + if total > target: + return + if total == target: + output.append(nums[:]) + + for i in range(start, len(candidates)): + nums.append(candidates[i]) + dfs(i, total + candidates[i]) + nums.pop() + + dfs(0, 0) + return output + """ + """ + 2. dp + dp[i]는 숫자들을 더해서 합이 i가 되는 모든 조합들을 저장합니다. + dp[num - candidate]에 있는 조합에 candidate를 추가하면 num을 만들 수 있으므로 확장합니다. + 중복된 조합을 방지하기 위해 candidates를 바깥 루프에 둠 (즉, 같은 숫자를 계속 재사용하되, 이전 숫자부터 누적). + + 시간 복잡도 (Time Complexity): + 바깥 루프: 후보 숫자만큼 → O(N) + 안쪽 루프: target까지 반복 → O(T) + 가장 안쪽 루프: dp[num - candidate]에 있는 조합들을 모두 순회 → O(A) (A는 조합 개수 및 길이에 비례) + → 따라서 최악의 경우 O(N × T × A) + + 공간 복잡도 (Space Complexity): + dp 배열 크기: target + 1 → O(T) + 각 dp[i]에 저장된 조합 리스트들의 개수와 길이 → O(A) + 따라서 전체 공간은 O(T × A) + + Example 2의 dp 출력: + + [[[]], [], [[2]], [], [], [], [], [], []] + [[[]], [], [[2]], [], [[2, 2]], [], [], [], []] + [[[]], [], [[2]], [], [[2, 2]], [], [[2, 2, 2]], [], []] + [[[]], [], [[2]], [], [[2, 2]], [], [[2, 2, 2]], [], [[2, 2, 2, 2]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [], [[2, 2, 2]], [], [[2, 2, 2, 2]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2]], [], [[2, 2, 2, 2]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2], [3, 3]], [], [[2, 2, 2, 2]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2], [3, 3]], [[2, 2, 3]], [[2, 2, 2, 2]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3]], [[2, 2, 2], [3, 3]], [[2, 2, 3]], [[2, 2, 2, 2], [2, 3, 3]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3], [5]], [[2, 2, 2], [3, 3]], [[2, 2, 3]], [[2, 2, 2, 2], [2, 3, 3]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3], [5]], [[2, 2, 2], [3, 3]], [[2, 2, 3], [2, 5]], [[2, 2, 2, 2], [2, 3, 3]]] + [[[]], [], [[2]], [[3]], [[2, 2]], [[2, 3], [5]], [[2, 2, 2], [3, 3]], [[2, 2, 3], [2, 5]], [[2, 2, 2, 2], [2, 3, 3], [3, 5]]] + """ + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + dp = [[] for _ in range(target + 1)] + dp[0] = [[]] + + for candidate in candidates: + for num in range(candidate, target + 1): + for combination in dp[num - candidate]: + dp[num].append(combination + [candidate]) + return dp[target] diff --git a/decode-ways/hu6r1s.py b/decode-ways/hu6r1s.py new file mode 100644 index 000000000..16130b727 --- /dev/null +++ b/decode-ways/hu6r1s.py @@ -0,0 +1,68 @@ +class Solution: + def numDecodings(self, s: str) -> int: + """ + 1. 재귀 알고리즘 사용 + _ + 226 -> B, 26 + _ + 26 -> B, 6 + _ + 6 -> F "BBF" + __ + 26 -> Z "BZ" + __ + 226 -> V, 6 + _ + 6 -> F "VF" + + 시간복잡도: O(2^n) - 중복 계산이 많아 매우 비효율적 + 공간복잡도: O(n) - 최대 재귀 깊이만큼 스택 사용 + """ + # def dfs(start): + # if start == len(s): + # return 1 + # if s[start] == "0": + # return 0 + # if start + 1 < len(s) and int(s[start:start+2]) < 27: + # return dfs(start+1) + dfs(start+2) + # else: + # return dfs(start+1) + # return dfs(0) + + """ + 2. 재귀 + 메모리제이션 + 시간복잡도: O(n) - 각 시작 위치에 대해 한 번만 계산 + 공간복잡도: O(n) - 메모이제이션 딕셔너리와 재귀 스택 + """ + # memo = {len(s): 1} + # def dfs(start): + # if start in memo: + # return memo[start] + # if s[start] == "0": + # memo[start] = 0 + # elif start + 1 < len(s) and int(s[start:start+2]) < 27: + # memo[start] = dfs(start+1) + dfs(start+2) + # else: + # memo[start] = dfs(start+1) + + # return memo[start] + # return dfs(0) + + """ + 3. DP + 시간복잡도 (Time Complexity): O(n) + - 문자열 s의 길이만큼 한 번의 루프를 도는 DP 방식 + + 공간복잡도 (Space Complexity): O(n) + - 길이 n+1짜리 dp 배열 사용 + - 공간 최적화를 하면 O(1)로 줄일 수 있음 + """ + dp = [0] * len(s) + [1] + for i in range(len(s)-1, -1, -1): + if s[i] == "0": + dp[i] = 0 + elif i + 1 < len(s) and int(s[i:i+2]) < 27: + dp[i] = dp[i+1] + dp[i+2] + else: + dp[i] = dp[i+1] + return dp[0] diff --git a/maximum-subarray/hu6r1s.py b/maximum-subarray/hu6r1s.py new file mode 100644 index 000000000..d5099edd6 --- /dev/null +++ b/maximum-subarray/hu6r1s.py @@ -0,0 +1,31 @@ +class Solution: + """ + 부분합 활용 + dp[0] = nums[0] + dp[1] = dp[0] + nums[1] 와 nums[1] 중 큰 값을 넣는다. + [-2, 1]까지의 합과 [1]까지의 합 중 큰 값을 넣는다고 생각하면 된다. + dp[1] = [1] + dp[2] = dp[1] + nums[2] 와 nums[2] 중 큰 값을 넣는다. + dp[1]에서 [-2, 1]를 선택했다면 [-2, 1, -3]까지의 합과 [1]을 선택했다면 [1, -3]까지의 합 중 큰 값을 선택하게 된다. + dp[2] = [1, -3] + dp[3] = dp[2] + nums[3] 와 nums[3] 중 큰 값을 넣는다. + dp[3]은 [1, -3]에 [4]를 추가하여 [1, -3, 4]까지의 합과 nums[3]인 4를 비교하여 큰 값으로 넣는다. + 결국 점화식은 dp[i] = max(dp[i-1] + nums[i], nums[i])가 된다. + + 시간 복잡도 (Time Complexity): + - dp 배열을 채우기 위해 한 번 순회: O(n) + - dp 배열에서 최댓값을 찾기 위해 한 번 순회: O(n) + → 총 시간 복잡도: O(n) + + 공간 복잡도 (Space Complexity): + - dp 배열이 입력 크기만큼 필요: O(n) + → 총 공간 복잡도: O(n) + """ + def maxSubArray(self, nums: List[int]) -> int: + dp = [0] * len(nums) + dp[0] = nums[0] + + for i in range(1, len(nums)): + dp[i] = max(dp[i-1] + nums[i], nums[i]) + + return max(dp) diff --git a/number-of-1-bits/hu6r1s.py b/number-of-1-bits/hu6r1s.py new file mode 100644 index 000000000..cf65060c4 --- /dev/null +++ b/number-of-1-bits/hu6r1s.py @@ -0,0 +1,17 @@ +class Solution: + """ + 1. n이 11일때, 이진수로 변환해보면 1011이다. set bits는 1의 값을 찾는 것이기에 1을 카운트해준다. + 시간 복잡도 (Time Complexity): + - bin(n): 정수를 이진 문자열로 변환 → O(log n) + (n의 크기에 따라 필요한 비트 수만큼 연산함) + - count('1'): 문자열에서 '1'의 개수를 세기 위해 전체 순회 → O(log n) + (이진 문자열의 길이는 log₂(n)에 비례) + 최종 시간 복잡도: O(log n) + + 공간 복잡도 (Space Complexity): + - bin(n)의 결과로 생성된 이진 문자열을 저장 → O(log n) + - 그 외 별도의 추가 공간 없음 + 최종 공간 복잡도: O(log n) + """ + def hammingWeight(self, n: int) -> int: + return bin(n).count('1') diff --git a/valid-palindrome/hu6r1s.py b/valid-palindrome/hu6r1s.py new file mode 100644 index 000000000..386d4850a --- /dev/null +++ b/valid-palindrome/hu6r1s.py @@ -0,0 +1,38 @@ +class Solution: + """ + 1. 문자열을 반복문을 돌며 isdigit() or isalpha()이 True라면 string에 붙이기 + 반복문이 끝나면 소문자로 모두 변경하고 뒤집은 문자와 같으면 True 반환 + 시간 복잡도 (Time Complexity): + - 문자열 순회: O(n) + - 문자열 덧붙이기(string += i): O(n^2) ← 파이썬에서 문자열은 불변이라 매번 새로운 문자열 생성 + - 소문자 변환 및 슬라이싱 비교: 각각 O(n) + 최종 시간 복잡도: O(n^2) + + 공간 복잡도 (Space Complexity): + - 필터링된 문자열 저장용 string: O(n) + - 역순 문자열 생성: O(n) + 최종 공간 복잡도: O(n) + + 2. 문자열과 숫자인 것만 뽑아 리스트에 넣기 + 시간 복잡도 (Time Complexity): + - 리스트 컴프리헨션: O(n) + - 각 문자에 대해 isalnum() → O(1), lower() → O(1) 이므로 전체 O(n) + - 리스트 슬라이싱 및 비교(string == string[::-1]): O(n) + 총 시간 복잡도: O(n) + + 공간 복잡도 (Space Complexity): + - 리스트 string에 최대 n개의 문자 저장: O(n) + - 슬라이싱된 string[::-1]도 새로운 리스트를 생성하므로 O(n) + 총 공간 복잡도: O(n) + """ + def isPalindrome(self, s: str) -> bool: + # string = "" + + # for i in s: + # if i.isdigit() or i.isalpha(): + # string += i + # string = string.lower() + # return True if string == string[::-1] else False + + string = [i.lower() for i in s if i.isalnum()] + return True if string == string[::-1] else False