-
-
Notifications
You must be signed in to change notification settings - Fork 358
[okyungjin] WEEK 01 Solutions #2653
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
Changes from all commits
49a0bb8
55c4063
70603ec
73c84c8
bacad1d
c234284
49687fa
14cb43c
d0e0bc2
5ab6b3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 두 풀이가 함께 있어서 가독성 비교하기 좋았던 거 같습니다! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # 문제: https://leetcode.com/problems/contains-duplicate/ | ||
|
|
||
| # 아이디어: | ||
| # 1. nums를 정렬한 다음 [1, len(nums)) 범위를 순회하며, 앞 순서의 번호와 일치하는지 확인한다. | ||
| # 2. set 자료구조 사용해서 탐색한 숫자를 기록한다. for문을 돌며 이전에 탐색한 숫자라면 early retrun | ||
|
|
||
|
|
||
| # 1) 정렬 사용, 이 경우에는 정렬에 의해 시간 복잡도 O(nlogN), 공간복잡도 O(1) | ||
| class Solution: | ||
| def containsDuplicate(self, nums: List[int]) -> bool: | ||
| nums.sort() # 정렬에 의해 시간 복잡도 O(nlogN) | ||
|
|
||
| for idx in range(1, len(nums)): | ||
| if nums[idx] == nums[idx-1]: | ||
| return True | ||
|
|
||
| return False | ||
|
|
||
|
|
||
| # 2) set 사용, 시간 복잡도 O(N), 공간 복잡도 O(N) | ||
| class Solution: | ||
| def containsDuplicate(self, nums: List[int]) -> bool: | ||
| seen = set() # 탐색한 숫자를 기록하는 set | ||
|
|
||
| for n in nums: | ||
| if n in seen: | ||
| return True | ||
| seen.add(n) | ||
|
|
||
| return False |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 연속 여부를 판단하며 두 이전 상태만 유지하는 최적화된 동적계획법 구현입니다. 개선 제안: 현재 구현이 적절해 보입니다. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,31 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # https://leetcode.com/problems/house-robber/description/ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # 훔칠 수 있는 최대 금액을 구한다 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # 단 연속된 집을 훔치면 경찰에 체포됨 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # 앞집, 앞앞집의 최대 수익을 아래 변수들에 기록한다 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # prev2, prev1, curr | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Time: O(N) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # Space: O(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| class Solution: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| def rob(self, nums: List[int]) -> int: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| prev2 = 0 # 앞앞집 최대 수익 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| prev1 = 0 # 앞집 최대 수익 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| for num in nums: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # 현재 최대 수익은 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # 1) 앞집 수익 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # 2) 앞앞집 수익 + 현재수익 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| curr = max(prev1, prev2 + num) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # 다음 턴을 위해 값을 슬라이딩(?) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # prev3 prev2 prev1 curr | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # a b c | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # a b c | ||||||||||||||||||||||||||||||||||||||||||||||||||
| prev2 = prev1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| prev1 = curr | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
로직이 직관적이어서 좋네요! prev1, prev2에 대한 주석은 선언 시점에만 있어도 잘 전달되는 것 같고 슬라이딩 관련된 로직은 주석이 없어도 충분히 의미가 전달되는 거 같습니다 :) |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # 순회 끝나면 직전 최대 수익이 prev1에 저장됨 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return prev1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 정렬 후 순회로 연속 부분 수열의 길이를 계산합니다. 중복 처리도 포함되어 있습니다. 개선 제안: 가능하면 해시셋 기반의 선형 해법으로 개선 여지가 있습니다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| from typing import List | ||
|
|
||
| # [문제] | ||
| # https://leetcode.com/problems/longest-consecutive-sequence/description/ | ||
|
|
||
| # [요구사항] | ||
| # - 정수 배열 nums가 주어진다. | ||
| # - nums의 숫자를 사용해서 연속된 숫자를 만들 수 있는 가장 긴 길이를 반환한다. | ||
| # - 시간 복잡도가 O(N)인 알고리즘을 작성해야 한다. > 정렬 불가능 | ||
|
|
||
| # [접근법] | ||
| # 문제 요구사항에 O(N)으로 풀게 되어있지만 당장 떠오르는 방식 | ||
| # 1. 배열을 오름차순으로 정렬한다. | ||
| # 2. 정렬된 배열을 순회하며 연속된 숫자일 경우 curLen, maxLen을 갱신하며 가장 긴 길이를 구한다. | ||
| # 3. 같은 숫자가 나올 경우 curLen을 유지하며 다음 루프로 넘긴다. | ||
| # 4. for문이 종료되면 가장 긴 길이인 maxLen을 반화한다. | ||
|
|
||
| # [더 알아볼 것] | ||
| # - O(N)으로 푸는 방법 고민해보기 | ||
|
|
||
|
|
||
| # Time: O(n log n) | ||
| # Space: O(N) | ||
| class Solution: | ||
| def longestConsecutive(self, nums: List[int]) -> int: | ||
| if not nums: | ||
| return 0 | ||
|
|
||
| nums.sort() # Time: O(NlogN) | ||
|
|
||
| maxLen = 1 | ||
| curLen = 1 | ||
|
|
||
| for idx in range(1, len(nums)): | ||
| # 중복 숫자는 길이에 포함하지 않음 | ||
| if nums[idx] == nums[idx - 1]: | ||
| continue | ||
|
|
||
| # 이전 숫자 + 1이면 연속 | ||
| if nums[idx] == nums[idx - 1] + 1: | ||
| curLen += 1 | ||
| else: | ||
| curLen = 1 | ||
|
|
||
| maxLen = max(maxLen, curLen) | ||
|
|
||
| return maxLen | ||
|
|
||
|
|
||
| # Solution().longestConsecutive([1,100]) | ||
| # Solution().longestConsecutive([0,3,7,2,5,8,4,6,0,1]) | ||
| # Solution().longestConsecutive([]) |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
풀이 1:
|
| 유저 분석 | 실제 분석 | 결과 | |
|---|---|---|---|
| Time | O(nlogK) | O(n log k) | ✅ |
| Space | O(N) | O(n) | ✅ |
피드백: Counter를 활용한 간결한 구현이며, 가장 많이 쓰이는 접근 방식입니다.
개선 제안: 현재 구현이 적절해 보입니다.
풀이 2: SolutionB.topKFrequent — Time: ✅ O(nlogN) → O(n log n) / Space: ✅ O(N) → O(n)
| 유저 분석 | 실제 분석 | 결과 | |
|---|---|---|---|
| Time | O(nlogN) | O(n log n) | ✅ |
| Space | O(N) | O(n) | ✅ |
피드백: 간단하지만 n이 커질 때 비효율적일 수 있습니다.
개선 제안: 현재 구현이 적절해 보입니다.
풀이 3: SolutionC.topKFrequent — Time: ✅ O(N) → O(n) / Space: ✅ O(N) → O(n)
| 유저 분석 | 실제 분석 | 결과 | |
|---|---|---|---|
| Time | O(N) | O(n) | ✅ |
| Space | O(N) | O(n) | ✅ |
피드백: 빈도별 버킷을 사용해 선형 시간 복잡도로 상위 k개를 찾습니다.
개선 제안: 현재 구현이 적절해 보입니다.
풀이 4: SolutionD.topKFrequent — Time: ❌ O(N logN) → O(n log k) / Space: ✅ O(N) → O(n)
| 유저 분석 | 실제 분석 | 결과 | |
|---|---|---|---|
| Time | O(N logN) | O(n log k) | ❌ |
| Space | O(N) | O(n) | ✅ |
피드백: 메모리 대비 속도 면에서 균형 있는 방법입니다.
개선 제안: 현재 구현이 적절해 보입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다양한 풀이 시도해보신 것 멋지네요! most_common 은 몰랐는데 이런게 있군요! 덕분에 배워갑니다
제공되는 함수들이 실제로 어떻게 구현되어 있는지 살펴보는 것 좋은 거 같아요. 저도 파이썬의 sorted 구현체를 살펴보려고 합니다 :)
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| from collections import Counter | ||
| import heapq | ||
| from typing import List | ||
|
|
||
| # [요구사항] | ||
| # - 정수 배열 nums과 정수 k가 주어진다. | ||
| # - 가장 자주 나타나는 k개의 숫자를 반환한다. | ||
| # - 결과는 아무 순서로 정렬해도 된다. | ||
|
|
||
| # [접근법] | ||
| # 1. for문을 순회하며 숫자의 빈도수를 계산한다 | ||
| # 2. 빈도를 내림차순으로 정렬해서 상위 k개를 추출한다. | ||
|
|
||
| # [더 알아볼 것] | ||
| # - most_common(k)의 시간 복잡도가 왜 O(nlogK)일까? | ||
|
|
||
| # [알게된 것] | ||
| # - 역순 탐색은 `for n in reversed(nums)` 구문으로 간결하게 | ||
| # - collections에 Counter를 사용하면 간결한 구문으로 빈도를 셀 수 있다 | ||
| # - Counter는 most_common() 메서드를 제공한다. | ||
| # - dict는 `counter.get(n, 0)`를 사용해서 기본값을 편하게 설정할 수 있다. | ||
|
|
||
|
|
||
| # Solution A: collections Counter 사용해서 집계 후 most_common 메서드 사용 | ||
| # - Time: O(nlogK) TODO | ||
| # - Space: O(N) | ||
| # - Runtime: 2ms / Beats 91.48% | ||
| # - Memory: 22.83MB / Beats 60.93% | ||
| class SolutionA: | ||
| def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||
| # Counter가 counter 이터러블을 반환함 | ||
| counter = Counter(nums) # Space: O(N) | ||
|
|
||
| # most_common 메서드로 빈도수 높은 k개 추출 -> (num, count) 튜플 | ||
| # Testcase1에서 counter.most_common(k)는 [(1, 3), (2, 2)]을 반환한다. | ||
| # for 문으로 튜플을 까서 num만 사용한다 | ||
| return [num for num, _ in counter.most_common(k)] # Time: O(nlogK) | ||
|
|
||
| # Solution B: 빈도수 집계 -> 내림차순 정렬 -> k개 슬라이스 | ||
| # - Time: O(nlogN) | ||
| # - Space: O(N) | ||
| # - Runtime: 3ms / Beats 89.31% | ||
| # - Memory: 22.75MB / Beats 80.99% | ||
| class SolutionB: | ||
| def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||
| counter: dict[int, int] = {} # Space: O(N) | ||
|
|
||
| # 숫자의 빈도수를 기록 | ||
| for n in nums: | ||
| counter[n] = counter.get(n, 0) + 1 # dict 기본값 함수 | ||
|
|
||
| # 빈도수로 내림차순 정렬 후 k개 슬라이스 | ||
| return sorted(counter, key=counter.get, reverse=True)[:k] # Time: O(nlogN) by 정렬 | ||
|
|
||
| # Solution C: 빈도별 숫자 목록을 저장하는 freqList 생성 (count sort) | ||
| # - Time: O(N) | ||
| # - Space: O(N) | ||
| # - Runtime: 8ms / Beats 33.04% | ||
| # - Memory: 22.89MB / Beats 60.93% | ||
| class SolutionC: | ||
| def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||
| # 숫자별 카운트 집계 | ||
| counter: dict[int, int] = {} # Space: O(N) | ||
| maxCnt = 1 | ||
| for n in nums: | ||
| counter[n] = counter.get(n, 0) + 1 | ||
| maxCnt = max(counter[n], maxCnt) # 최대 빈도수를 기록한다 (freqList 최적화를 위함) | ||
|
|
||
| # maxCount 길이인 [[], [], ..., []] 배열을 미리 생성해둔다 | ||
| # nums[idx]는 빈도수가 idx + 1인 숫자들이 들어간다. | ||
|
|
||
| freqList = [[] for _ in range(maxCnt)] | ||
|
|
||
| for num, cnt in counter.items(): | ||
| freqList[cnt - 1].append(num) | ||
|
|
||
| # 예시) | ||
| # nums=[1, 1, 1, 2, 2, 3], k=2 이라면 1은 3번, 2는 2번, 3은 1번 등장한다. | ||
| # freqList는 [[3], [2], [1]] 가 된다. | ||
|
|
||
| result = [] # Space: O(N) | ||
| for bucket in reversed(freqList): # Time: O(N), 역순 탐색은 reversed로 간결하게 | ||
| if len(result) == k: # 해가 유니크하므로 등치로 비교 | ||
| return result # result도 slice 안 하고 그대로 반환 | ||
| result.extend(bucket) | ||
|
|
||
| return result | ||
|
|
||
| # Solution D: 파이썬의 heapq 라이브러리 사용하여 최소힙으로 정렬 | ||
| # - Time: O(N logN) | ||
| # - Space: O(N) | ||
| # - Runtime: 7ms / Beats 51.93% | ||
| # - Memory: 23.00MB / Beats 42.12% | ||
| class Solution: | ||
| def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||
| counter = Counter(nums) | ||
|
|
||
| # heapq 라이브러리는 최소힙으로 구현되어 있어서 | ||
| # 우선순위를 높아려면 count를 음수로 저장해야함 | ||
| heap = [] | ||
| for num, count in counter.items(): | ||
| heapq.heappush(heap, (-count, num)) | ||
|
|
||
| result = [] | ||
| for _ in range(k): | ||
| _, num = heapq.heappop(heap) | ||
| result.append(num) | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| print(Solution().topKFrequent([1, 1, 1, 2, 2, 3], 2)) # [1, 2] | ||
| # print(Solution().topKFrequent([1], 1)) # [1] | ||
| # print(Solution().topKFrequent([1, 2, 1, 2, 1, 2, 3, 1, 3, 2], 2)) # [1, 2] | ||
| # print(Solution().topKFrequent([4, 4, 4, 6, 6, 7], 1)) # [4] | ||
| # print(Solution().topKFrequent([1, 1, 2, 0, 0, 0], 2)) # [0, 1] | ||
| # print(Solution().topKFrequent([-1, -1, -1, 2, 2, 3], 2)) # [-1, 2] |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 해시맵 활용으로 선형 시간에 해결하는 표준 풀이입니다. 개선 제안: 현재 구현이 적절해 보입니다.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # 링크: https://leetcode.com/problems/two-sum/ | ||
|
|
||
| # [요구사항] | ||
| # - 정수 배열 nums에서 두 숫자를 더했을 때 target이 되는 두 수의 "인덱스"를 반환한다. | ||
| # - 반환하는 배열의 순서는 아무렇게나 | ||
| # - 같은 원소를 두 번 사용하면 안 된다. | ||
| # - ex) nums: [3,3] / target: 6 일 때 [0, 1]은 정답, [0, 0]은 오답 | ||
| # - 단 하나의 해만 존재함 | ||
|
|
||
| # [접근법] | ||
| # 1. hashMap을 선언 | ||
| # 2. 배열을 순회하며 숫자를 hashMap에 담는다 | ||
| # 3. 현재 숫자와 합쳐서 target이 되는 숫자가 hashMap에 있는지 탐색 | ||
| # 4. step3을 만족하는 숫자가 있으면 즉시 반환, 아니면 hashMap에 저장 | ||
|
|
||
| # [복잡도] | ||
| # - Time: O(N), for문 순회하는 비용 | ||
| # - Space: O(N) | ||
|
|
||
| class Solution: | ||
| def twoSum(self, nums: List[int], target: int) -> List[int]: | ||
| num_map = {} # Space: O(N) | ||
|
|
||
| for i, num in enumerate(nums): # Time: O(N) | ||
| pair = target - num # 쌍을 만족하는 숫자 추출 | ||
| if pair in num_map: # 쌍을 만족하는 숫자가 이미 존재하면 즉시 종료 | ||
| return [i, num_map[pair]] | ||
| num_map[num] = i |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 정렬 풀이와 해시셋 풀이 두 가지가 제시되어 있습니다. 두 방법 모두 한 번의 선형 스캔으로 중복 여부를 확인합니다.
개선 제안: 현재 구현이 적절해 보입니다.