Skip to content

[taurus09318976] WEEK 03 Solutions #1329

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 2 commits into from
Apr 20, 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
71 changes: 71 additions & 0 deletions combination-sum/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#이 문제는 1)현재 합이 target과 같으면 결과에 추가 후 되돌아감, 2) 현재 합이 target을 초과하면 더 이상 진행하지 않고 되돌아감,
#3) 각 candidate에 대해 recursive하게 시도

class Solution:
def combinationSum(self, candidates: List[int], target: int):
#결과를 저장할 리스트
result = []

#start: 현재 선택할 수 있는 candidate의 시작 인덱스
#current: 현재까지 선택한 숫자들의 리스트
#current_sum: 현재까지 선택한 숫자들의 합
def backtrack(start, current, current_sum):
#현재 합이 target과 같으면 현재 조합을 결과에 추가 후 되돌아감
if current_sum == target:
result.append(current.copy())
return
#현재 합이 target을 초과하면 더 이상 진행하지 않고 되돌아감
if current_sum > target:
return

#start 파라미터를 사용하여 중복 조합이 생기지 않도록 이전에 선택한 숫자부터 다시 시작
for i in range(start, len(candidates)):
#현재 candidate 선택
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

라인별로 주석을 달아주셔서 너무 깔끔하게 잘 읽히네요!
알고리즘 한해서 이런식으로 작성해놓은건 좋은 사례인것 같습니다 👍

여기 for문에서 backTrack을 재귀적으로 호출할 때

current_sum + candidates[i] > target 인 경우(현재 탐색 경로가 이미 target을 넘어버린 경우)에

이후 for문을 수행을 하지 않음으로써 가지치기를 하는 방법을 추가하면
더욱 빠른 알고리즘이 될 것 같습니다!

current.append(candidates[i])
#recursive 호출 : i를 그대로 전달하여 같은 숫자를 무한반복하여 사용할 수 있음.
backtrack(i, current, current_sum + candidates[i])
#마지막에 선택한 숫자 제거
current.pop()

#백트래킹 시작
backtrack(0, [], 0)
#모든 가능한 조합 반환
return result


# 테스트 케이스
solution = Solution()

# 테스트 1
test1_candidates = [2,3,6,7]
test1_target = 7
print(f"Expected: [[2,2,3],[7]]")
print(f"Result: {solution.combinationSum(test1_candidates, test1_target)}")

# 테스트 2
test2_candidates = [2,3,5]
test2_target = 8
print(f"Expected: [[2,2,2,2],[2,3,3],[3,5]]")
print(f"Result: {solution.combinationSum(test2_candidates, test2_target)}")

# 테스트 3
test3_candidates = [2]
test3_target =
print(f"Expected: []")
print(f"Result: {solution.combinationSum(test3_candidates, test3_target)}")


#시간 복잡도: O(2^target)
#각 단계에서 숫자를 선택하거나 선택하지 않는 두 가지 선택지가 있음
#최악의 경우, target을 만들기 위해 모든 가능한 조합을 시도해야 함.
#예를 들어 candidates = [1], target = 10일 때:
#[1,1,1,1,1,1,1,1,1,1]까지 시도해야 함
#이는 2^10에 가까운 경우의 수가 발생
#공간 복잡도: O(target)
#각 재귀 호출마다 새로운 스택 프레임이 생성됨
#최대 깊이는 target을 가장 작은 숫자로 나눈 값임
#예를 들어 target = 7, 리스트 내 가장 작은 숫자 = 2일 때
#7/2 = 3.5 → 최대 3번의 재귀 호출
#[2,2,3]을 만들 때 3번의 재귀 호출
#공간 복잡도는 target 값에 비례하여 증가하므로 O(target)임
#이는 target이 커질수록 더 많은 메모리가 필요하다는 것을 의미함
80 changes: 80 additions & 0 deletions decode-ways/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#계단 오르기 문제, 피보나치 수열과 동일한 패턴의 동적 프로그래밍 문제임
#차이점은 피보나치 수열은 0,1로 시작하며 나머지 두 문제는 1,1로 시작한다는 점
#디코딩 문제는 1)0이 아니어야 하며, 2)두 자리 숫자가 10~26 사이여야 한다는 조건이 주어진다는 점이 다름

class Solution:
def numDecodings(self, s: str):
#초기 예외값 처리. 빈 문자열이거나, 첫 문자가 0이면 디코딩 불가능
if not s or s[0] == '0':
return 0

#DP 변수 초기화
##dp[i-2] 역할
prev = 1
##dp[i-1] 역할
curr = 1

#DP 계산
##첫번째 인덱스부터 마지막까지
for i in range(1, len(s)):
##현재 위치의 디코딩 방법 수를 임시로 저장
temp = 0

##한 자리 숫자로 디코딩하는 경우
###현재 숫자가 0이 아니면
if s[i] != '0':
###이전 방법 수를 더함
temp += curr

##두 자리 숫자로 디코딩하는 경우
###현재와 이전 숫자를 합친 두 자리 수가 10~26 사이면
if 10 <= int(s[i-1:i+1]) <= 26:
###두 자리 전 방법 수를 더함
temp += prev

##변수 업데이트
###이전 값을 두 자리 전 값으로 이동
prev = curr
###현재 값을 새로 계산된 값으로 이동
curr = temp

#최종 디코딩 방법 수
return curr

# 테스트 케이스
solution = Solution()

# 테스트 1
test1 = "12"
print(f"Expected: 2")
print(f"Result: {solution.numDecodings(test1)}")

# 테스트 2
test2 = "226"
print(f"Expected: 3")
print(f"Result: {solution.numDecodings(test2)}")

# 테스트 3
test3 = "06"
print(f"Expected: 0")
print(f"Result: {solution.numDecodings(test3)}")


#시간 복잡도: O(n)
#n = 입력 문자열 s의 길이
#for 루프가 문자열의 길이만큼 한 번 실행됨
#각 반복에서 수행하는 연산:
#s[i] != '0' 비교: O(1)
#int(s[i-1:i+1]) 변환: O(1)
#10 <= two_digit <= 26 비교: O(1)
#변수 업데이트: O(1)
#따라서 전체 시간 복잡도는 O(n)
#공간 복잡도: O(1)
#추가로 사용하는 공간:
#prev: O(1)
#curr: O(1)
#temp: O(1)
#i: O(1)
#입력 크기 n과 무관하게 상수 개수의 변수만 사용
#따라서 공간 복잡도는 O(1)

55 changes: 55 additions & 0 deletions maximum-subarray/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#이 문제는 카데인 알고리즘(Kadane's Algorithm) 문제로
#원래 배열에서 연속된 요소들로만 구성된 최대 부분 배열 합을 찾는 문제임

class Solution:
def maxSubArray(self, nums: List[int]):
#max_sum: 지금까지 발견한 가장 큰 부분 배열의 합
#current_sum: 현재 고려 중인 부분 배열의 합
#위의 둘을 첫번째 요소로 초기화
max_sum = current_sum = nums[0]

#두 번째 요소부터 시작
for num in nums[1:]:
#현재 숫자를 포함한 새로운 부분 배열의 합 계산
current_sum = max(num, current_sum + num)
#num: 현재 숫자만으로 새로운 부분 배열 시작
#current_sum + num: 기존 부분 배열에 현재 숫자 추가

#최대 합 업데이트
max_sum = max(max_sum, current_sum)

return max_sum

# 테스트 케이스
solution = Solution()

# 테스트 1
test1 = [-2,1,-3,4,-1,2,1,-5,4]
print(f"Expected: 6")
print(f"Result: {solution.maxSubArray(test1)}")

# 테스트 2
test2 = [1]
print(f"Expected: 1")
print(f"Result: {solution.maxSubArray(test2)}")

# 테스트 3
test3 = [5,4,-1,7,8]
print(f"Expected: 23")
print(f"Result: {solution.maxSubArray(test3)}")


#시간 복잡도: O(n)
#n = nums의 길이
#for 루프가 배열의 길이만큼 한 번 실행됨
#각 반복에서 수행하는 연산:
#max(num, current_sum + num): O(1)
#max(max_sum, current_sum): O(1)
#따라서 전체 시간 복잡도는 O(n)임
#공간 복잡도: O(1)
#추가로 사용하는 공간:
#max_sum: O(1)
#current_sum: O(1)
#num: O(1)
#입력 크기 n과 무관하게 상수 개수의 변수만 사용
#따라서 공간 복잡도는 O(1)임
50 changes: 50 additions & 0 deletions number-of-1-bits/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#이 문제는 1) 주어진 정수를 이진수로 변환, 2) 변환된 이진수에서 1의 개수를 세기 순서로 해결해야 함

class Solution:
def hammingWeight(self, n: int):
count = 0
while n:
#n의 가장 오른쪽 비트가 1인지 확인, 1이면 count를 증가시킴
if n & 1 == 1 :
count += 1
#다음 비트를 검사하기 위해 n을 오른쪽으로 1비트씩 시프트함
n = n >> 1
return count



# 테스트 케이스
solution = Solution()

# 테스트 1
test1 = 11
print(f"Expected: 3")
print(f"Result: {solution.hammingWeight(test1)}")

# 테스트 2
test2 = 128
print(f"Expected: 1")
print(f"Result: {solution.hammingWeight(test2)}")

# 테스트 3
test3 = 2147483645
print(f"Expected: 30")
print(f"Result: {solution.hammingWeight(test3)}")


#cf. n & 0일 때 예: n = 1011
# 1011
#& 0000
#-----------
# 0000 -> 0과 AND 연산을 하면 어떤 수든지 결과가 항상 0이 됨
#반면에 n & 1을 사용하는 경우 1의 이진수 표현이 0001이기 때문에
#이 연산으로 n의 가장 오른쪽 비트만 확인할 수 있음.

#시간 복잡도 분석: O(log n)
#while n 루프는 1의 개수와는 무관하게, n의 이진수 자릿수만큼 반복됨.
#예: n = 128 (10000000)일 때 8번 반복
#일반적으로 n의 이진수 자릿수는 log₂n
#n & 1: O(1), n >> 1: O(1)의 경우 상수 시간만큼 걸림

#공간 복잡도 분석: O(1)
#count 변수의 공간복잡도는 O(1)이며, n을 수정하면서 사용하므로 추가적인 배열이나 리스트가 필요하지 않음
48 changes: 48 additions & 0 deletions valid-palindrome/taurus09318976.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#이 문제를 해결하기 위해서는 1) 문자열을 소문자로 변환, 2) 알파벳과 숫자만 남기고 다른 문자 제거,
#3) 문자열이 palindrome인지 확인

class Solution:
def isPalindrome(self, s: str):
# 빈 리스트 extracted_string 생성
extracted_string = []


for i in s:
#isalnum() 메서드를 사용해 알파벳과 숫자만 필터링함
if i.isalnum():
#조건에 맞으면, lower() 메서드로 문자열을 소문자로 변환하고 알파벳과 숫자만 남김
extracted_string.append(i.lower())
#extracted_string 리스트의 모든 요소를 ''제거 후 하나의 문자열로 결합
extracted_string = ''.join(extracted_string)

# palindrome인지 확인
return extracted_string == extracted_string[::-1]

# 테스트 케이스
solution = Solution()

# 테스트 1
test1 = "A man, a plan, a canal: Panama"
print(f"Expected: True")
print(f"Result: {solution.isPalindrome(test1)}")

# 테스트 2
test2 = "race a car"
print(f"Expected: False")
print(f"Result: {solution.isPalindrome(test2)}")

# 테스트 3
test3 = " "
print(f"Expected: True")
print(f"Result: {solution.isPalindrome(test3)}")


#시간 복잡도 : O(n), 입력 문자열 s의 모든 문자를 한 번씩 순회함(n = 입력 문자열의 길이)
#for c in s 루프: O(n). 입력 문자열 s의 모든 문자를 한 번씩 순회하므로 O(n)
#isalnum(), lower(), append()의 경우 : O(1), 각 문자에 대해 상수 시간이 걸림
#'filtered == filtered[::-1]', '.join(filtered)'의 경우 : O(m) (m = 필터링된 문자열의 길이)
#m ≤ n, 최악의 경우 m = n

#공간 복잡도 분석: O(n), 모든 문자가 알파벳이나 숫자인 최악의 경우 m = n
#filtered 리스트: O(m)
#filtered[::-1]: O(m)