Skip to content
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
87 changes: 87 additions & 0 deletions 3sum/JinuCheon.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, Sorting
  • 설명: 최적해는 정렬 후 두 포인터(left, right)로 스캔하는 Two Pointers 패턴을 사용합니다. 중복 제거를 위한 중복 건너뛰기도 포함되며, 정렬 자체가 탐색의 전제이므로 Greedy 성격의 선택 방식이 나타납니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n^2)
Space O(1)

피드백: 정렬 후 각 i에 대해 왼쪽/오른쪽 포인터를 이동시키며 중복을 건너뛰는 방식으로 시간복잡도를 O(n^2)로 줄였고, 중복 조합을 제거하기 위한 로직이 포함되어 있습니다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# brute force 로 풀어본 답안. O(n3) 다. 역시 timeout.
# dic, set 자료형 익히기 겸 해봤다. 파이썬 편하다 최고.
class Solution:
def threeSum(self, nums: list[int]) -> list[list[int]]:
n = len(nums)
result = set()

for i in range(n):
for j in range(n):
for k in range(n):
if (i == j or j == k or i == k):
continue
if (nums[i] + nums[j] + nums[k] == 0):
result.add(tuple(sorted([nums[i], nums[j], nums[k]])))

return list(result)

# LLM 에게 힌트 얻어서 진행.
# i != j, i != k, j != k 조건 때문에 정렬을 하면 안된다고 착각하고 있었다. 사실 고정된 순서는 중요하지 않음.
# 정렬을 진행하고, two pointer 로 진행.
# 통과!
class Solution2:
def threeSum(self, nums: list[int]) -> list[list[int]]:
# 정렬
nums.sort()
n = len(nums)
result = set()

for i in range(n):
# i 자리 중복 skip (이전 원소와 같으면 건너뜀)
if i > 0 and nums[i] == nums[i - 1]:
continue

# i + left + right === 0 이 되어야 함.
left = i + 1
right = n - 1

while left < right:
if nums[left] + nums[right] == -nums[i]:
result.add(tuple([nums[i], nums[left], nums[right]]))
left += 1
right -= 1

elif nums[left] + nums[right] < -nums[i]:
left += 1
else:
right -= 1

return list(result)

# LLM의 피드백.
# 중복을 스킵하는 효율적인 방법이 있었음.
class Solution:
def threeSum(self, nums: list[int]) -> list[list[int]]:
nums.sort()
n = len(nums)
result = []

for i in range(n):
if i > 0 and nums[i] == nums[i - 1]:
continue

if nums[i] > 0:
break

left, right = i + 1, n - 1
target = -nums[i]

while left < right:
s = nums[left] + nums[right]
if s == target:
result.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1

# left, right 자리도 중복 skip
while left < right and nums[left] == nums[left - 1]:
left += 1
while left < right and nums[right] == nums[right + 1]:
right -= 1

elif s < target:
left += 1
else:
right -= 1

return result
53 changes: 53 additions & 0 deletions climbing-stairs/JinuCheon.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
  • 설명: 피보나치 형태로 계단 수를 구하는 문제로, 초기 재귀는 분할 정복/중복 순환이지만, 메모이제이션과 DP 배열로 최적화되는 과정이 핵심이다. 부분 문제의 해를 합쳐 전체 해를 구하는 DP 패턴이 주로 사용된다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 3가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Solution3.climbStairs — Time: O(n) / Space: O(n)
복잡도
Time O(n)
Space O(n)

피드백: 메모이제이션/DP를 사용해 피보나치 계열의 계단 수를 선형 시간에 계산합니다.

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

풀이 2: Solution2.climbStairs — Time: ❌ O(1) → O(n) / Space: O(n)
유저 분석 실제 분석 결과
Time O(1) O(n)
Space - O(n) -

피드백: 해당 접근은 이미 계산된 값 재사용으로 중복 계산을 피합니다.

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

풀이 3: Solution.climbStairs — Time: O(2^n) / Space: O(n)
복잡도
Time O(2^n)
Space O(n)

피드백: 입력 크기에 따라 지수 시간 복잡도와 깊은 재귀 스택이 발생합니다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 재귀를 만드는 것이 잘 생각나지 않아서, 이렇게 저렇게 시도해보다 결국 LLM 도움.
# 그렇지만 제출 결과 Time Out.
# 2^N 복잡도임. max 45 의 경우 35,184,372,088,832.
class Solution:
def climbStairs(self, n: int) -> int:
# 1 -> 한칸만 가능
# 2 -> (1,1) or (2)
if n <= 2:
return n;

# 1칸 진행한 케이스 + 2칸 진행한 케이스
return self.climbStairs(n-1) + self.climbStairs(n-2);

# memonization 적용. 실무를 하고나서 이걸 보니, 캐싱이라고 부르고 싶다.
# 1~N 숫자 하나당 한번의 계산을 하게 되니, 시간복잡도는 O(1) 이다.
# 이게 왜 easy 난이도지? 잊어먹었다가 주말에 자력으로 풀어보자.
class Solution:
def climbStairs(self, n: int) -> int:
self.memo = {}
return self.dfs(n)

def dfs(self, n: int) -> int:
if n <= 2:
return n

# 이미 해당 수를 계산한 적이 있다면 early return.
if n in self.memo:
return self.memo[n]

# 새로운 결과가 있으면 무조건 저장.
self.memo[n] = self.dfs(n - 1) + self.dfs(n - 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.

이 부분이 핵심인데요.
재귀 호출 없이 이 점화식 구현하시면 될 것 같습니다.

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.

@yuseok89 유석님 감사합니다!
점화식.. 어떤 뜻인지도 정확하게 모르고 있었는데 덕분에 찾아보고 공부할 기회가 되었네요.

따뜻한 리뷰 감사합니다 :)

# yuseok89 님 피드백 반영.
# 점화식(recursive relation): 수열의 항을 그 이전 항들을 이용해서 정의하는 식.
# 함수 호출 오버헤드 제거 & 안정성
class Solution3:
    def climbStairs(self, n: int) -> int:
        if n < 2:
            return n
        
        dp = [0] * (n+1)

        # set init values
        dp[1] = 1
        dp[2] = 2

        # i 번째 계단 오르는 경우의 수: 1칸 + 2칸 경우의 수의 합
        for i in range(3, n+1):
            dp[i] = dp[i - 1] + dp[i - 2]
        
        return dp[n];

return self.memo[n]


# yuseok89 님 피드백 반영.
# 점화식(recursive relation): 수열의 항을 그 이전 항들을 이용해서 정의하는 식.
# 함수 호출 오버헤드 제거 & 안정성
class Solution3:
def climbStairs(self, n: int) -> int:
if n < 2:
return n

dp = [0] * (n+1)

# set init values
dp[1] = 1
dp[2] = 2

# i 번째 계단 오르는 경우의 수: 1칸 + 2칸 경우의 수의 합
for i in range(3, n+1):
dp[i] = dp[i - 1] + dp[i - 2]

return dp[n];
33 changes: 33 additions & 0 deletions product-of-array-except-self/JinuCheon.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, Dynamic Programming, Hash Map / Hash Set
  • 설명: 두 패턴은 누적곱을 이용한 전후 누적합(곱)으로 결과를 구성하는 기법이며, 배열의 각 위치에 대해 좌우의 누적 값을 미리 계산해 최종 값을 얻는 방식이다. 이 문제의 핵심은 추가 메모 없이 한 번에 해결하는 아이디어로, DP적 누적값 관리의 예이다.

📊 시간/공간 복잡도 분석

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

피드백: 좌우 누적곱을 이용해 2-pass로 제조합을 구하는 최적화된 풀이를 제시합니다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 처음에 직관적으로 떠오른 O(n2) 풀이.
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
result = [1] * n
for i in range(n):
for j in range(n):
if i == j:
continue
result[j] *= nums[i]

return result

# 고민하다가 LLM 에게 힌트 달라고 질문.
# 좌 / 우 누적곱으로 처리하는 방식.
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(num)
result = [1] * n

# 왼쪽 누적곱을 result에 미리 채워둠
cumulativeLeftSum = 1
for i in range(n):
result[i] = cumulativeLeftSum
cumulativeLeftSum *= nums[i]

# 오른쪽 누적곱을 곱해나가면서 완성
cumulativeRightSum = 1
for i in range(n - 1, -1, -1):
result[i] *= cumulativeRightSum
cumulativeRightSum *= nums[i]

return result
39 changes: 39 additions & 0 deletions valid-anagram/JinuCheon.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Hash Map / Hash Set, Bit Manipulation
  • 설명: 주 코드들은 문자 빈도 수를 해시 맵으로 비교하는 방식으로 작동하며, Counter도 내부적으로 해시 맵을 사용합니다. 또한 정렬 기반 해법은 O(n log n)로 비교하지만 패턴은 주로 해시 맵 사용에 해당합니다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 3가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Solution3.isAnagram — Time: O(n) / Space: O(Alphabet)
복잡도
Time O(n)
Space O(Alphabet)

피드백: 두 문자열의 문자 빈도수를 카운트하는 간단하고 직관적인 방법입니다.

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

풀이 2: Solution2.isAnagram — Time: O(n) / Space: O(k)
복잡도
Time O(n)
Space O(k)

피드백: s에서 각 문자를 증가시키고 t에서 감소시키며 최종 값이 모두 0인지 확인합니다.

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

풀이 3: Solution.isAnagram — Time: O(n log n) / Space: O(1)
복잡도
Time O(n log n)
Space O(1)

피드백: 정렬 시간을 포함하면 일반적으로 느린 편이지만 직관적입니다.

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# my own solution
# O(n)
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
count1 = {}
for ch in s:
count1[ch] = count1.get(ch, 0) + 1

count2 = {}
for ch in t:
count2[ch] = count2.get(ch, 0) + 1

return count1 == count2

# gpt suggestion
### half space
#### O(n)
class Solution2:
def isAnagram(self, s: str, t: str) -> bool:
count = {}
for ch in s:
count[ch] = count.get(ch, 0) + 1
for ch in t:
count[ch] = count.get(ch, 0) - 1
return all(v == 0 for v in count.values())

### simple solution (What??)
#### O(n)
#### Counter works like my own solution internally. But I'm ganna forgot this, becuase I'm learning now.
from collections import Counter
class Solution3:
def isAnagram(self, s: str, t: str) -> bool:
return Counter(s) == Counter(t)

### using sort
#### O(n log n)
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
return sorted(s) == sorted(t)
Loading