From 5a2d2a132a4bfb1c5303090caf87ec334c6af5bf Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 15 Nov 2025 08:48:08 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20valid-anagram=20=ED=92=80=EC=9D=B4?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20(Counter=20=ED=95=B4=EC=8B=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- valid-anagram/unpo88.py | 73 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 valid-anagram/unpo88.py diff --git a/valid-anagram/unpo88.py b/valid-anagram/unpo88.py new file mode 100644 index 0000000000..ff3f1b8ed4 --- /dev/null +++ b/valid-anagram/unpo88.py @@ -0,0 +1,73 @@ +from collections import Counter + + +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + if len(s) != len(t): + return False + + return Counter(s) == Counter(t) +""" +================================================================================ +풀이 과정 - 08:27 시작 ~ 08:32 종료 (5분 소요) +================================================================================ + +1. s, t 문자열이 주어지는데 +2. s와 t가 anagram이면 True, 그렇지 않으면 False 반환 +3. anagram이란 어떤 단어나 구의 글자들을 모두 한 번씩만 사용하여 순서를 바꾸어 새롭게 만든 단어나 구 +4. 그러면 문자 정렬을 해서 같은지 비교를 하는 방법이 있을 것 같고 +5. 근데 그렇게 하면 문자 정렬에 시간 복잡도가 증가하니까 +6. 각 문자의 개수를 카운팅하는 해싱 Counter를 이용해서 +7. 서로의 키 값이 동일한지 확인한다거나 하면 조금 더 빠르게 처리가 가능할듯? + + +[1차 시도] 반복문으로 직접 비교 +──────────────────────────────────────────────────────────────────────────────── +8. Counter로 각 문자의 개수를 세서 비교하는 방식 구현 + + if len(s) != len(t): + return False + s_count = Counter(s) + t_count = Counter(t) + + for key, value in s_count.items(): + if value != t_count[key]: + return False + return True + +9. 정상적으로 통과되는 것 확인 완료 + + +[2차 개선] Counter 객체 직접 비교 +──────────────────────────────────────────────────────────────────────────────── +10. Counter 객체끼리 직접 비교가 가능하다는 것을 알게 됨 +11. 반복문 제거하고 더 간결하게 개선 + + if len(s) != len(t): + return False + + return Counter(s) == Counter(t) + +12. 코드가 더 간결해지고 가독성도 향상됨 +13. 최종 통과 확인 완료 +14. 근데 그럼에도 len 길이를 비교하는것은 O(1) 측면에서 좋다는 답변도 받음 + +[다른 풀이 탐구] Counter를 사용하지 않고 직접 딕셔너리를 만들어서 처리한다면? +──────────────────────────────────────────────────────────────────────────────── + counter = {} + if len(s) != len(t): + return False + + for char in s: + counter[char] = counter.get(char,0) + 1 + + for char in t: + counter[char] = counter.get(char,0) - 1 + if counter[char] < 0: + return False + + return True + +[다른 풀이 탐구] s와 t는 소문자만 받아올 수 있는데 그러면, 배열로 소문자 개수만큼 인덱싱해서 처리하는 방법도? +──────────────────────────────────────────────────────────────────────────────── +""" \ No newline at end of file From aadc5753346d285e965e8650f65d65ee23aae386 Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 15 Nov 2025 09:02:58 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20climbing-stairs=20=ED=92=80?= =?UTF-8?q?=EC=9D=B4=20=EC=B6=94=EA=B0=80=20(=EA=B3=B5=EA=B0=84=EB=B3=B5?= =?UTF-8?q?=EC=9E=A1=EB=8F=84=20=EC=B5=9C=EC=A0=81=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- climbing-stairs/unpo88.py | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 climbing-stairs/unpo88.py diff --git a/climbing-stairs/unpo88.py b/climbing-stairs/unpo88.py new file mode 100644 index 0000000000..d027336dd7 --- /dev/null +++ b/climbing-stairs/unpo88.py @@ -0,0 +1,63 @@ +class Solution: + def climbStairs(self, n: int) -> int: + if n <= 2: + return n + + prev2, prev1 = 1, 2 + + for i in range(3, n + 1): + current = prev1 + prev2 + prev2 = prev1 + prev1 = current + + return prev1 + +""" +================================================================================ +풀이 과정 - 08:49 시작 ~ 08:54 종료 (5분 소요) +================================================================================ + +1. 계단 올라갈건데, n step씩 올라감 +2. 1 또는 2단계씩 갈 수 있는데, 얼마나 많이 탑까지 올라갈 수 있는 방법이 있을까? +3. 재귀로 내가 1 또는 2로 갈 수 있는 방법을 모두 구해야할 것 같은데? +4. n - 1, n - 2 +5. 3번째 계단부터 첫 번째 계단 갈 수 있는 방법 + 두 번째 계단 갈 수 있는 방법 +6. DP로 풀어야할 것 같은데? +7. 이거 계단 경우의 수 보니까 피보나치 수열이네? + + +[1차 시도] 딕셔너리를 사용한 DP +──────────────────────────────────────────────────────────────────────────────── +7. 피보나치 수열과 유사한 패턴 발견 +8. dp[i] = dp[i-1] + dp[i-2] 점화식 도출 +9. 기저 사례: dp[1] = 1, dp[2] = 2 + + dp = { 1: 1, 2: 2} + for i in range(3, n + 1): + dp[i] = dp[i-1] + dp[i-2] + + return dp[n] + +10. 정상적으로 통과되는 것 확인 완료 + + +[2차 개선] 공간 복잡도 최적화 +──────────────────────────────────────────────────────────────────────────────── +11. 잘 생각해보면 공간 복잡도를 아예 딕셔너리를 안만들고 푸는 방법도 있을듯? +13. 변수 2개(prev2, prev1)로 공간 복잡도를 O(1)로 개선 + + if n <= 2: + return n + + prev2, prev1 = 1, 2 + + for i in range(3, n + 1): + current = prev1 + prev2 + prev2 = prev1 + prev1 = current + + return prev1 + +14. 공간 복잡도 O(n) → O(1)로 개선 완료 +15. 최종 통과 확인 완료 +""" From cfbecb78bc67df624ae24562d0c2918943781148 Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 15 Nov 2025 10:39:49 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20product-of-array-except-self=20?= =?UTF-8?q?=ED=92=80=EC=9D=B4=20=EC=B6=94=EA=B0=80=20(=EA=B3=B5=EA=B0=84?= =?UTF-8?q?=EB=B3=B5=EC=9E=A1=EB=8F=84=20=EC=B5=9C=EC=A0=81=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- product-of-array-except-self/unpo88.py | 123 +++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 product-of-array-except-self/unpo88.py diff --git a/product-of-array-except-self/unpo88.py b/product-of-array-except-self/unpo88.py new file mode 100644 index 0000000000..44e5bf8861 --- /dev/null +++ b/product-of-array-except-self/unpo88.py @@ -0,0 +1,123 @@ +class Solution: + def productExceptSelf(self, nums: list[int]) -> list[int]: + n = len(nums) + answer = [1] * n + + left = 1 + for i in range(n): + answer[i] = left + left *= nums[i] + + right = 1 + for i in range(n-1, -1, -1): + answer[i] *= right + right *= nums[i] + + return answer + +""" +================================================================================ +풀이 과정 - 09:43 시작 ~ 풀이 과정 떠올리지 못함 +================================================================================ + +1. 정수 배열 nums가 주어짐 +2. 배열 answer를 반환해야 하는데 +3. answer[i]는 nums의 모든 요소의 곱과 같다 (nums[i]를 제외한) +4. O(n) 시간에 실행되는 알고리즘을 작성해야하고, 나누기 연산을 사용해서는 안된다 +5. 나누기 연산을 어떻게 이용 안하고 풀지? + +──────────────────────────────────────────────────────────────────────────────── +6. Claude 도움 → answer[i] = (i 왼쪽의 곱) * (i 오른쪽의 곱) + + +[1차 시도] 왼쪽/오른쪽 곱 배열 사용 +──────────────────────────────────────────────────────────────────────────────── +7. 각 위치의 왼쪽 누적곱과 오른쪽 누적곱을 미리 계산 +8. left[i] = nums[0] * ... * nums[i-1] +9. right[i] = nums[i+1] * ... * nums[n-1] +10. answer[i] = left[i] * right[i] + + n = len(nums) + left = [1] * n + for i in range(1, n): + left[i] = left[i-1] * nums[i-1] + + right = [1] * n + for i in range(n-2, -1, -1): + right[i] = right[i+1] * nums[i+1] + + return [left[i] * right[i] for i in range(n)] + +11. 정상적으로 통과되는 것 확인 완료 + + +[2차 개선] 공간 복잡도 최적화 +──────────────────────────────────────────────────────────────────────────────── +12. left, right 배열을 따로 만들면 O(n) 공간 복잡도 추가 사용 +13. answer 배열을 재활용하면 추가 공간 없이 해결 가능 +14. 첫 번째 순회: answer에 왼쪽 누적곱 저장 +15. 두 번째 순회: answer에 오른쪽 누적곱을 곱하면서 최종 결과 완성 + + n = len(nums) + answer = [1] * n + + left = 1 + for i in range(n): + answer[i] = left + left *= nums[i] + + right = 1 + for i in range(n-1, -1, -1): + answer[i] *= right + right *= nums[i] + + return answer + +16. 추가 공간 복잡도 O(n) → O(1)로 개선 완료 (answer 배열 제외) +17. 최종 통과 확인 완료 + + +[문제 복기] 패턴 발견 과정 +──────────────────────────────────────────────────────────────────────────────── +- 문제를 다시 복기해보면 +- 각 위치에서 자기 자신을 제외한 모든 원소의 곱을 구해야함 +- 제약을 무시하면 이중 반복문 Brute Force로 풀 수 있음 +- 제약을 무시하면 나눗셈을 이용해서도 풀 수 있음 +- 나눗셈도 금지되고 O(n) 시간에 풀어야한다면? +- "제외한다"를 어떻게 표현해주면 좋을까? (이 부분이 핵심) + +작은 예시로 패턴 찾기: + nums = [2, 3, 4] + + answer[0] = 3 * 4 = 12 + answer[1] = 2 * 4 = 8 + answer[2] = 2 * 3 = 6 + + answer[0] = (없음) * (3 * 4) = 12 + answer[1] = (2) * (4) = 8 + answer[2] = (2 * 3) * (없음) = 6 + +- 패턴 발견 → 왼쪽 부분 * 오른쪽 부분 +- 해법 도출 → 왼쪽 부분의 모든 곱 * 오른쪽 부분의 모든 곱 + +패턴을 일반화하는 과정이 필요: + answer[i] = nums[0] * ... * nums[i-1] * nums[i+1] * ... * nums[n-1] + left[i] = nums[0] * ... * nums[i-1] + right[i] = nums[i+1] * ... * nums[n-1] + answer[i] = left[i] * right[i] + +점화식 찾는데, DP적 사고 필요: + 왼쪽 누적곱: + left[1] = nums[0] + left[2] = nums[0] * nums[1] = left[1] * nums[1] + left[3] = nums[0] * nums[1] * nums[2] = left[2] * nums[2] + → left[i] = left[i-1] * nums[i-1] + + 오른쪽 누적곱: + right[2] = nums[3] + right[1] = nums[3] * nums[2] = right[2] * nums[2] + right[0] = nums[3] * nums[2] * nums[1] = right[1] * nums[1] + → right[i] = right[i+1] * nums[i+1] + +관련해서 코드로 구현하는 과정이 필요함 +""" From 493acb66f2bdc9744721028291390829af0ea2ce Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 15 Nov 2025 15:33:29 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=203sum=20=ED=92=80=EC=9D=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(Two=20Pointer=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3sum/unpo88.py | 192 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 3sum/unpo88.py diff --git a/3sum/unpo88.py b/3sum/unpo88.py new file mode 100644 index 0000000000..56c75e318e --- /dev/null +++ b/3sum/unpo88.py @@ -0,0 +1,192 @@ +class Solution: + def threeSum(self, nums: list[int]) -> list[list[int]]: + nums.sort() + result = set() + + for i in range(len(nums) - 2): + # 양수인 경우 더 이상 탐색할 필요가 없음 + if nums[i] > 0: + break + + # i 중복 skip + if i > 0 and nums[i] == nums[i - 1]: + continue + + left = i + 1 + right = len(nums) - 1 + + while left < right: + total = nums[i] + nums[left] + nums[right] + + if total == 0: + result.add((nums[i], nums[left], nums[right])) + + # 중복 건너뛰기 + while left < right and nums[left] == nums[left + 1]: + left += 1 + # 중복 건너뛰기 + while left < right and nums[right] == nums[right - 1]: + right -= 1 + left += 1 + right -= 1 + elif total < 0: + left += 1 + else: + right -= 1 + + return [list(t) for t in result] + + +""" +================================================================================ +풀이 과정 - 10:51 시작 +================================================================================ + +1. 정수 배열 nums가 주어지면 +2. 모든 세 쌍 [nums[i], nums[j], nums[k]]을 반환해야한다 +3. i != j, i != k, j != k 여야하고 +4. nums[i] + nums[j] + nums[k] == 0 이어야한다 +5. 중복된 세 쌍이 포함되지 않아야한다 + + +[1차 시도] Brute Force 3중 반복문 +──────────────────────────────────────────────────────────────────────────────── +6. 제약을 무시하고 생각해보면 Brute Force 3중 반복문으로 찾을 수 있을 것 같음 +7. 중복도 제거해야하니까 마지막에 set을 두고 처리 + + result = [] + n = len(nums) + + for i in range(n): + for j in range(i+1, n): + for k in range(j+1, n): + if nums[i] + nums[j] + nums[k] == 0: + triplet = sorted([nums[i], nums[j], nums[k]]) + if triplet not in result: + result.append(triplet) + + return result + +8. Time Limit Exceeded 발생 +9. O(n³) 시간 복잡도로 너무 느림 + + +[2차 시도] Two Sum 응용 (HashSet) +──────────────────────────────────────────────────────────────────────────────── +10. 3개를 동시에 찾으려니까 어려운 것 같은데 +11. 2개를 먼저 구하고, 나머지 하나를 더하면 0이 되는지를 확인해볼까 +12. 두 수의 합 = 나머지 하나의 값 +13. -nums[i] = nums[j] + nums[k] +14. 그럼 Two Sum 문제로 풀 수 있을듯? +15. 두 개의 합이 set에 존재하는지를 체크하는 형태로 가보자 + + result = set() + + for i in range(len(nums)): + seen = set() + for j in range(i + 1, len(nums)): + complement = -(nums[i] + nums[j]) + if complement in seen: + result.add(tuple(sorted([nums[i], nums[j], complement]))) + seen.add(nums[j]) + + return [list(x) for x in result] + +16. 아 근데 이것도 Time Limit Exceeded가 발생하네 +17. O(n²) 시간 복잡도로 개선했지만 sorted() 호출과 set 연산이 추가 비용 발생 +18. 다른 접근 방법이 필요할 듯 + + +[3차 시도] Two Pointer 방식 +──────────────────────────────────────────────────────────────────────────────── +19. 그럼 Two Pointer 방식으로 풀어야할 것 같은데 +20. Two Pointer 방식을 사용하려면 정렬이 필요함 +21. Two Sum의 Two Pointer 패턴을 생각해보자 +22. 근데 3개를 동시에 찾으려면 어떻게 해야할까? +23. i를 고정하고, 나머지에서 Two Sum으로 합이 -nums[i]인 두 수를 찾는다! + + 정렬 전: [-1, 0, 1, 2, -1, -4] + 정렬 후: [-4, -1, -1, 0, 1, 2] + index: 0 1 2 3 4 5 + + i = 0, left = i + 1, right = len(nums) - 1 + [-4, -1, -1, 0, 1, 2] + i L R + + i = 1: + [-4, -1, -1, 0, 1, 2] + i L R + +25. 정렬 먼저 한 후 합의 크기에 따라 포인터를 이동 + + nums.sort() + result = set() + + for i in range(len(nums) - 2): + left = i + 1 + right = len(nums) - 1 + + while left < right: + total = nums[i] + nums[left] + nums[right] + + if total == 0: + result.add((nums[i], nums[left], nums[right])) + left += 1 + right -= 1 + elif total < 0: + left += 1 + else: + right -= 1 + + return [list(t) for t in result] + +25. O(n²) 시간 복잡도 (정렬 O(n log n) + 반복문 O(n²)) +26. 정상적으로 통과되는 것 확인 완료 +27. 근데 이 방식으로 풀었는데 느리다 + + +[4차 개선] 중복 제거 최적화 +──────────────────────────────────────────────────────────────────────────────── +28. Claude에게 물어보니 tuple 생성할 때 상수 배수가 큰 것 같다고 함 +29. 중복을 미리 스킵해주는 방법을 알려줌 +30. i가 양수인 경우 더 이상 탐색할 필요 없음 (정렬되어 있으므로) +31. i, left, right 중복 건너뛰기 추가 + + nums.sort() + result = set() + + for i in range(len(nums) - 2): + # 양수인 경우 더 이상 탐색할 필요가 없음 + if nums[i] > 0: + break + + # i 중복 skip + if i > 0 and nums[i] == nums[i - 1]: + continue + + left = i + 1 + right = len(nums) - 1 + + while left < right: + total = nums[i] + nums[left] + nums[right] + + if total == 0: + result.add((nums[i], nums[left], nums[right])) + + # 중복 건너뛰기 + while left < right and nums[left] == nums[left + 1]: + left += 1 + while left < right and nums[right] == nums[right - 1]: + right -= 1 + left += 1 + right -= 1 + elif total < 0: + left += 1 + else: + right -= 1 + + return [list(t) for t in result] + +32. 중복 처리 최적화로 속도 개선 +33. 최종 통과 확인 완료 +""" \ No newline at end of file From 841d91bc50af2146f86b0dbe35a61252c657f6f7 Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 15 Nov 2025 15:51:07 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20validate-binary-search-tree=20?= =?UTF-8?q?=ED=92=80=EC=9D=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- validate-binary-search-tree/unpo88.py | 51 +++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 validate-binary-search-tree/unpo88.py diff --git a/validate-binary-search-tree/unpo88.py b/validate-binary-search-tree/unpo88.py new file mode 100644 index 0000000000..f82f03fd5f --- /dev/null +++ b/validate-binary-search-tree/unpo88.py @@ -0,0 +1,51 @@ +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def isValidBST(self, root: Optional[TreeNode]) -> bool: + def validate(node, min_val, max_val): + if not node: + return True + + if node.val <= min_val or node.val >= max_val: + return False + + return (validate(node.left, min_val, node.val) and + validate(node.right, node.val, max_val)) + + return validate(root, float('-inf'), float('inf')) + + +""" +================================================================================ +풀이 과정 +================================================================================ + +[1차 시도] 단순 재귀 +──────────────────────────────────────────────────────────────────────────────── +4. left < node < right 체크를 해줘야하고 +5. 각 노드는 자신의 서브트리에서 최소값과 최대값을 가지고 있어야함 +6. 현재 노드보다 왼쪽은 작은 값, 오른쪽은 큰 값 +7. 재귀적으로 구현할 수 있음 + + def validate(node, min_val, max_val): + if not node: + return True + + if node.val <= min_val or node.val >= max_val: + return False + + # 재귀적으로 왼쪽, 오른쪽 서브트리 체크 + # 왼쪽 서브트리는 현재 노드 값보다 작아야하고, 오른쪽 서브트리는 현재 노드 값보다 커야함 + return (validate(node.left, min_val, node.val) and + validate(node.right, node.val, max_val)) + + return validate(root, float('-inf'), float('inf')) + +8. 시간 복잡도: O(n) - 모든 노드를 한 번씩 방문 +9. 공간 복잡도: O(h) - 재귀 호출 스택 (h는 트리 높이) +10. 재귀적으로 스택 오버플로우가 발생하는 것이 우려되면 iterative로 구현할 수 있음 +""" From 89ddd124946a6515406372ad6f92034ecc6afc39 Mon Sep 17 00:00:00 2001 From: "whatsup@lemonbase.com" Date: Sat, 15 Nov 2025 16:00:53 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat:=20=EC=A4=84=EB=B0=94=EA=BF=88=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3sum/unpo88.py | 2 +- valid-anagram/unpo88.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/3sum/unpo88.py b/3sum/unpo88.py index 56c75e318e..7e6dd02ee9 100644 --- a/3sum/unpo88.py +++ b/3sum/unpo88.py @@ -189,4 +189,4 @@ def threeSum(self, nums: list[int]) -> list[list[int]]: 32. 중복 처리 최적화로 속도 개선 33. 최종 통과 확인 완료 -""" \ No newline at end of file +""" diff --git a/valid-anagram/unpo88.py b/valid-anagram/unpo88.py index ff3f1b8ed4..f118e686ea 100644 --- a/valid-anagram/unpo88.py +++ b/valid-anagram/unpo88.py @@ -70,4 +70,4 @@ def isAnagram(self, s: str, t: str) -> bool: [다른 풀이 탐구] s와 t는 소문자만 받아올 수 있는데 그러면, 배열로 소문자 개수만큼 인덱싱해서 처리하는 방법도? ──────────────────────────────────────────────────────────────────────────────── -""" \ No newline at end of file +"""