Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
51 changes: 51 additions & 0 deletions combination-sum/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

답 둘 다 정확한 풀이네요. 👍

한 가지 궁금한 게, 실제 제출은 DP로 하고 백트래킹을 주석으로 남기신 이유가 있을까요?

이 문제(모든 조합 나열)는 출력 자체가 지수적으로 커질 수 있어서, DP 쪽에 적힌 O(N*target) / O(target)이 실제보다 낙관적으로 보여요.

dp[j].append(partial + [c]) 부분이 매번 리스트를 통째로 복사하고, dp[0..target] 각 칸에 중간 조합들을 다 들고 있어서 시간/공간 모두 그 표기보단 커질 것 같아요. 오히려 주석 처리된 백트래킹이 path 하나를 push/pop으로 재활용해서 메모리 면에선 더 유리해 보이는데, 혹시 DP를 고른 특별한 이유가 있으면 궁금합니다!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

맞아요, 리스트를 통으로 복사하는 과정에서 오버헤드가 생깁니다!
다만 백트래킹은 target의 값이 커지면 커질수록 exponential하게 시간복잡도가 커지기 때문에 사실상 큰 값에서는 사용하기 어려울것이 있고, 그에 비해서 trade-off를 고려했을때 DP가 좀 더 낫다고 판단했어요!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

또한 백트래킹과 달리 재귀를 쓰지 않으니 재귀 호출에 의한 스택 오버헤드도 피할수 있겠죠

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

재귀 스택 회피는 고려할만한 트레이드오프인 것 같네요 파이썬은 기본 재귀 한도가 1000으로 알고있어 target이 아주 크면 백트래킹이 RecursionError를 낼 수도 있으니, 이건 DP가 분명히 이득 보는 지점으로 생각됩니다. 🥇

다만 두 가지만 덧붙이면요. (1) 메모리는 재귀 스택 O(depth)보다 DP가 지는 짐(중간 조합 전부 저장 + partial 복사)이 더 커서, 총량은 백트래킹이 가볍습니다. (2) 시간은 결과 조합 수가 지수적이라 둘 다 같은 지수 하한이에요 — 이 부분엔 DP도 이득이 없어요. 그래서 강점이 서로 다른 쪽(DP=스택 안정, 백트래킹=메모리/상수)이라고 보는 게 정확할 것 같아요.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  1. 그건 확실히 그렇겠네요, 백트래킹은 정답을 바로바로 쌓아나가니 사실상 쓸모없이 쓰이는 메모리 공간이 거의 없을테니까요
  2. 그렇군요

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Dynamic Programming, Backtracking
  • 설명: 주석에 처음은 백트래킹으로 모든 조합을 탐색하는 전형적 패턴이 보이고, 두 번째 구현은 각 타겟 합에 대한 부분해를 저장하는 DP 방식으로 해를 구성합니다. 따라서 백트래킹과 동적계획법 두 패턴이 적용됩니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(N * target)
Space O(target)

피드백: 각 후보를 순회하며 목표 합까지의 모든 부분합에 대해 가능한 조합을 확장한다. dp 배열의 각 항목이 부분해를 담고 있어 시간은 후보 수와 목표합에 비례한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

"""
Time Complexity: O(N^target)
Space Complexity: O(target)

Classic backtracking approach.
- Use a helper function to backtrack and generate all possible combinations.
"""
# class Solution:
# def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
# N = len(candidates)
# candidates.sort()

# ans = []
# app = []

# def backtracking(last: int, left: int) -> None:
# if left == 0:
# ans.append(app[:])
# return

# for i in range(last, N):
# if candidates[i] > left:
# continue

# app.append(candidates[i])
# backtracking(i, left - candidates[i])
# app.pop()

# backtracking(0, target)

# return ans

"""
Time Complexity: O(N * target)
Space Complexity: O(target)

- Use a dynamic programming approach to store the combinations that sum to each target.
- dp[j] is a list of lists that sum to j.
"""
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
dp = [list() for _ in range(target + 1)]
dp[0] = [[]]

for c in candidates:
for j in range(c, target + 1):
for partial in dp[j - c]:
dp[j].append(partial + [c])

return dp[-1]
31 changes: 31 additions & 0 deletions decode-ways/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Dynamic Programming, Two Pointers
  • 설명: 주어진 코드는 문자열 s를 한 번 순회하면서 두 문자 단위로 26 이하의 조합 여부를 판단하고, 이전 상태들을 합산해 현재 상태를 갱신한다. 이는 문제 해결을 위해 연속 부분해를 저장하는 DP 패턴으로, Two Pointers처럼 인덱스 차를 유지하며 부분해를 누적 계산하는 방식의 변형이다.

📊 시간/공간 복잡도 분석

복잡도
Time O(N)
Space O(1)

피드백: 두 변수만으로 연속 상태를 유지하며 각 위치의 해석 경우의 수를 계산한다. 시작 조건과 0 처리에 주의해야 한다.

개선 제안: 필요하면 모듈러 연산이나 입력 검증 로직을 추가해 안정성을 높일 수 있습니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Time Complexity: O(N)
Space Complexity: O(1)

prev_2 : number of ways to decode the string ending with the previous two characters
prev_1 : number of ways to decode the string ending with the previous character
new_prev : number of ways to decode the string ending with the current character
"""
class Solution:
def numDecodings(self, s: str) -> int:
N = len(s)

if s[0] == "0":
return 0

if N == 1:
return 1

prev_2 = 1
prev_1 = int(1 <= int(s[0] + s[1]) <= 26) + int(s[1] != "0")

for i in range(2, N):
new_prev = 0
if 1 <= int(s[i - 1] + s[i]) <= 26 and s[i - 1] != "0":
new_prev += prev_2
if s[i] != "0":
new_prev += prev_1

prev_2, prev_1 = prev_1, new_prev

return prev_1
71 changes: 71 additions & 0 deletions maximum-subarray/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Dynamic Programming, Greedy
  • 설명: 해당 코드는 Kadane 알고리즘으로 연속 부분 배열의 최댓값을 구하는 방식으로, 이전 구간의 최댓값을 현재 원소와 합쳐 확장하는 점에서 DP의 최적 부분구조와 Greedy의 선택성을 활용합니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(N)
Space O(1)

피드백: 중첩 부분구조를 제거하고 연속 부분배열의 최댓값을 선형 시간에 구한다.

개선 제안: 음수 배열에 대해서도 안정적으로 동작하는지 테스트를 늘리면 좋습니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@


"""
Time Complexity: O(NLogN)
Space Complexity: O(N) ( Reason: Recursive stack pointer for every element )

Divide and Conquer Approach

1. Divide the array into two halves
2. Find the maximum subarray sum in the left half
3. Find the maximum subarray sum in the right half
4. Find the maximum subarray sum that crosses the midpoint
5. Return the maximum of the three sums
"""
# class Solution:
# def maxSubArray(self, nums: List[int]) -> int:

# def divide(start: int, end: int) -> int:
# if start >= end:
# return nums[start]

# mid = (start + end) // 2

# left = divide(start, mid)
# right = divide(mid + 1, end)

# left_largest = -float('inf')
# right_largest = -float('inf')

# left_acc = 0
# right_acc = 0

# left_index = mid
# right_index = mid + 1

# while left_index >= start:
# left_acc += nums[left_index]
# left_largest = max(left_largest, left_acc)
# left_index -= 1

# while right_index <= end:
# right_acc += nums[right_index]
# right_largest = max(right_largest, right_acc)
# right_index += 1

# return max(left_largest + right_largest, left, right)

# return divide(0, len(nums) - 1)




"""
Time Complexity: O(N)
Space Complexity: O(1)

prev : maximum sum of the subarray ending with the previous element
ans : maximum sum of the subarray
curr : maximum sum of the subarray ending with the current element
"""
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
N = len(nums)
prev = nums[0]
ans = nums[0]

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

return ans
11 changes: 11 additions & 0 deletions number-of-1-bits/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Bit Manipulation
  • 설명: 주어진 코드는 비트 연산과 비트 시프트로 1의 개수를 세는 방식이다. n의 최하위 비트를 확인하고(AND 1), 차례로 시프트하여 전체 자릿수를 검사한다.

📊 시간/공간 복잡도 분석

복잡도
Time O(숫자의 비트 수)
Space O(1)

피드백: 루프가 비트가 0이 될 때까지 수행되므로 입력 n의 비트 길이에 비례한다.

개선 제안: n & (n - 1) 기법을 사용하면 1비트를 제거하며 루프 횟수를 줄일 수 있습니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Solution:
def hammingWeight(self, n: int) -> int:
ans = 0
while n:
ans += n & 1
n >>= 1
return ans

# class Solution:
# def hammingWeight(self, n: int) -> int:
# return bin(n).count('1')
42 changes: 42 additions & 0 deletions valid-palindrome/alphaorderly.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Two Pointers, Greedy, Hash Map / Hash Set
  • 설명: 주요 로직은 좌우 포인터를 이용해 문자열의 양 끝에서 문자 비교를 하며 비알파숫문자 제거 및 소문자 변환을 수행한다. 이에 따라 Two Pointers 패턴이 주를 이루고, 불필요 캐릭터 건너뛰기 로직은 Greedy 성격의 단순한 전진으로 볼 수 있다.

📊 시간/공간 복잡도 분석

복잡도
Time O(N)
Space O(1)

피드백: 추가 문자열 생성 없이 제자리에서 검사하므로 공간 사용을 줄인다.

개선 제안: isalpha/isnumeric 검사를 더 엄밀히 하여 유효한 문자만 비교하도록 강화할 수 있습니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Time Complexity: O(N)
Space Complexity: O(N)

- Process string with lower() and isalnum() to remove non-alphanumeric characters
- Check if the processed string is a palindrome by comparing it to its reverse
"""
# class Solution:
# def isPalindrome(self, s: str) -> bool:
# processed = "".join(ch.lower() for ch in s if ch.isalnum())

# return processed == processed[::-1]

"""
Time Complexity: O(N)
Space Complexity: O(1)

- Use two pointers to check if the string is a palindrome
- Skip non-alphanumeric characters
- Compare characters from both ends towards the center
"""
class Solution:
def isPalindrome(self, s: str) -> bool:
N = len(s)

left, right = 0, N - 1

while True:
while not s[left].isalnum() and left < right:
left += 1
while not s[right].isalnum() and left < right:
right -= 1

if left >= right:
break

if s[left].lower() == s[right].lower():
left, right = left + 1, right - 1
else:
return False

return True
Comment on lines +22 to +42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

바깥 while True: + break 구조가 동작은 완벽한데, 조건을 위로 올리면 흐름이 더 읽기 쉬워질 것 같아요.

class Solution:
    def isPalindrome(self, s: str) -> bool:
        left, right = 0, len(s) - 1
        while left < right:
            while left < right and not s[left].isalnum():
                left += 1
            while left < right and not s[right].isalnum():
                right -= 1
            if s[left].lower() != s[right].lower():
                return False
            left, right = left + 1, right - 1
        return True

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

그렇네요, 차라리 얼리 리턴을 하는게 나았긴 하네요

Loading