Skip to content
Open
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
77 changes: 77 additions & 0 deletions combination-sum/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
result = []

def backtrack(start, current, current_sum):
if current_sum == target:
result.append(current[:])
return

if current_sum > target:
return

for i in range(start, len(candidates)):
current.append(candidates[i])
backtrack(i, current, current_sum + candidates[i])
current.pop()

backtrack(0, [], 0)
return result


"""
================================================================================
풀이 과정
================================================================================

1. 이거 모든 가능한 조합을 찾아야하는데 합이 target을 넘으면 중단되야하네?
2. 문제는 같은 숫자를 여러 번 쓸 수 있는건데..
3. 백트래킹으로 접근해야할 것 같은데..


[1차 시도] 백트래킹 (Backtracking)
────────────────────────────────────────────────────────────────────────────────
4. 모든 조합을 탐색하되, 조건에 맞지 않으면 가지치기
5. 같은 숫자를 여러 번 사용할 수 있으므로 재귀 호출 시 같은 인덱스부터 시작
6. 중복 조합 방지를 위해 start 인덱스 사용

result = []

def backtrack(start, current, current_sum):
# target을 만족하면 결과에 추가
if current_sum == target:
result.append(current[:]) # 복사해서 추가
return

# 가지치기: 합이 target 초과
if current_sum > target:
return

# 조합 탐색
for i in range(start, len(candidates)):
current.append(candidates[i])
# 같은 숫자 재사용 가능하므로 i부터 시작
backtrack(i, current, current_sum + candidates[i])
current.pop() # 백트래킹: 상태 복원

backtrack(0, [], 0)
return result

7. Example: candidates = [2,3,6,7], target = 7

backtrack(0, [], 0)
├─ 2 추가 → backtrack(0, [2], 2)
│ ├─ 2 추가 → backtrack(0, [2,2], 4)
│ │ ├─ 2 추가 → backtrack(0, [2,2,2], 6)
│ │ │ └─ 2 추가 → backtrack(0, [2,2,2,2], 8) → 8 > 7 return
│ │ └─ 3 추가 → backtrack(1, [2,2,3], 7) → 7 == 7 ✅ [2,2,3]
│ └─ 3 추가 → backtrack(1, [2,3], 5)
│ └─ ... (탐색 계속)
└─ 7 추가 → backtrack(3, [7], 7) → 7 == 7 ✅ [7]

8. 시간 복잡도: O(N^(T/M))
- N: candidates 길이
- T: target 값
- M: candidates의 최소값
9. 공간 복잡도: O(T/M) - 재귀 호출 스택 깊이
"""
66 changes: 66 additions & 0 deletions decode-ways/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
class Solution:
def numDecodings(self, s: str) -> int:
# 예외 처리: "0"으로 시작하면 불가능
if not s or s[0] == '0':
return 0

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

# 초기값
dp[0] = 1 # 빈 문자열
dp[1] = 1 # 첫 번째 문자 (이미 "0" 체크함)

for i in range(2, n + 1):
# 한 자리 숫자 (1~9)
if s[i-1] != '0':
dp[i] += dp[i-1]

# 두 자리 숫자 (10~26)
two_digit = int(s[i-2:i])
if 10 <= two_digit <= 26:
dp[i] += dp[i-2]

return dp[n]


"""
================================================================================
풀이 과정
================================================================================
- "226" → 2|2|6 (BBF), 22|6 (VF), 2|26 (BZ) → 3가지 방법 존재
- DP를 이용해서 각 위치에서 가능한 디코딩 경우의 수를 계산
- 한 자리(1~9)와 두 자리(10~26) 숫자를 고려하여 누적

[1차 시도]
────────────────────────────────────────────────────────────────────────────────
1. 접근 방법
- DP 배열 사용: dp[i] = 문자열의 처음부터 i번째까지의 디코딩 경우의 수
- 각 위치에서 한 자리 숫자(1~9)로 디코딩 가능하면 dp[i-1] 더하기
- 두 자리 숫자(10~26)로 디코딩 가능하면 dp[i-2] 더하기

2. 구현
n = len(s)
dp = [0] * (n + 1)

# 초기값 설정
dp[0] = 1 # 빈 문자열
dp[1] = 1 # 첫 번째 문자 (0이 아니면 1가지)

for i in range(2, n + 1):
# 한 자리 숫자 (1~9)
if s[i-1] != '0':
dp[i] += dp[i-1]

# 두 자리 숫자 (10~26)
two_digit = int(s[i-2:i])
if 10 <= two_digit <= 26:
dp[i] += dp[i-2]

3. 예외 처리
- "0"으로 시작하는 문자열은 디코딩 불가능 → 0 반환
- 빈 문자열 체크

4. 시간 복잡도: O(n) - 문자열을 한 번만 순회
5. 공간 복잡도: O(n) - DP 배열 사용
"""
63 changes: 63 additions & 0 deletions maximum-subarray/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
current_sum = nums[0]
max_sum = nums[0]

for i in range(1, len(nums)):
# 현재 값부터 새로 시작 vs 이전 합에 추가
current_sum = max(nums[i], current_sum + nums[i])
max_sum = max(max_sum, current_sum)

return max_sum


"""
================================================================================
풀이 과정
================================================================================

1. 부분 배열 중 가장 큰 합을 찾아야 하는데 어떻게 접근하지?
2. Sliding Window? → 음수/양수가 섞여있어서 윈도우 크기를 언제 조절할지 불명확
3. 모든 부분배열을 확인? → O(n²)이라 비효율적
4. 각 위치에서 "현재까지의 최대 부분합"을 추적하면 되지 않을까?


[1차 시도] Dynamic Programming
────────────────────────────────────────────────────────────────────────────────
5. 핵심 아이디어: 각 위치에서 "이 위치를 끝으로 하는" 최대 부분배열의 합 추적
6. current_sum = "현재 위치를 끝으로 하는 최대 부분합"
7. 매 위치에서 선택: 이전 합에 추가 vs 여기서 새로 시작

current_sum = nums[0] # 현재 위치까지의 최대 부분합
max_sum = nums[0] # 전체 최댓값

for i in range(1, len(nums)):
# 이전 합이 양수면 계속, 음수면 버리고 새로 시작
current_sum = max(nums[i], current_sum + nums[i])
max_sum = max(max_sum, current_sum)

return max_sum

8. Example: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

i=0: current=-2, max=-2
i=1: max(1, -2+1=-1) = 1 (새로 시작), max=1
i=2: max(-3, 1-3=-2) = -2 (이어감), max=1
i=3: max(4, -2+4=2) = 4 (새로 시작), max=4
i=4: max(-1, 4-1=3) = 3 (이어감), max=4
i=5: max(2, 3+2=5) = 5 (이어감), max=5
i=6: max(1, 5+1=6) = 6 (이어감), max=6 ✓
i=7: max(-5, 6-5=1) = 1 (이어감), max=6
i=8: max(4, 1+4=5) = 5 (이어감), max=6

결과: [4, -1, 2, 1] = 6

9. 왜 작동하는가?
- 모든 부분배열은 어딘가에서 끝남
- 각 위치에서 "여기를 끝으로 하는 최댓값" 추적
- 이전 합이 음수면 버리는 게 이득 (Greedy한 선택)
- 이전 합이 양수면 현재 값이 음수여도 계속 (예: 4 + (-1) = 3)

10. 시간 복잡도: O(n) - 배열을 한 번만 순회
11. 공간 복잡도: O(1) - 변수 2개만 사용
"""
24 changes: 24 additions & 0 deletions number-of-1-bits/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution:
def hammingWeight(self, n: int) -> int:
return bin(n).count('1')


"""
================================================================================
풀이 과정
================================================================================

1. 일단 이진법으로 변경이 필요하고
2. 변경된 이진법에서 1을 카운팅해야하네?


[1차 시도] 내장 함수 활용
────────────────────────────────────────────────────────────────────────────────
3. Python의 bin() 함수로 이진 문자열 변환
4. count() 메서드로 '1' 문자 개수 세기

return bin(n).count('1')

5. 시간 복잡도: O(log n) - 이진 표현의 비트 수만큼
6. 공간 복잡도: O(log n) - 이진 문자열 생성
"""
66 changes: 66 additions & 0 deletions valid-palindrome/unpo88.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
class Solution:
def isPalindrome(self, s: str) -> bool:
left = 0
right = len(s) - 1

while left < right:
if not s[left].isalnum():
left += 1
continue

if not s[right].isalnum():
right -= 1
continue

if s[left].upper() != s[right].upper():
return False

left += 1
right -= 1

return True


"""
================================================================================
풀이 과정
================================================================================
- A man, a plan, a canal: Panama
- 띄어쓰기는 무시하고 앞 뒤에서 똑같은지 체크를 해야하네?
- 앞 포인터와 뒷 포인터에서 시작해서 띄어쓰기 만나면 건너뛰고
- 틀린것 나오면 False 반환하고, False를 만난적 없으면 True 반환

[1차 시도] Two Pointer
────────────────────────────────────────────────────────────────────────────────
1. 접근 방법
- Two Pointer 사용: left는 앞에서, right는 뒤에서 시작
- 유효하지 않은 문자(알파벳/숫자가 아닌 것)는 건너뛰기
- 유효한 문자끼리 비교하며 중앙으로 이동

2. 구현
left = 0
right = len(s) - 1

while left < right:
# 왼쪽 포인터: 알파벳/숫자가 아니면 건너뛰기
if not s[left].isalpha() and not s[left].isnumeric():
left += 1
continue

# 오른쪽 포인터: 알파벳/숫자가 아니면 건너뛰기
if not s[right].isalpha() and not s[right].isnumeric():
right -= 1
continue

# 대소문자 무시하고 비교
if s[left].upper() != s[right].upper():
return False

left += 1
right -= 1

return True

4. 시간 복잡도: O(n) - 문자열을 한 번만 순회
5. 공간 복잡도: O(1) - 추가 공간 사용 없음 (포인터 2개만 사용)
"""