diff --git a/contains-duplicate/okyungjin.py b/contains-duplicate/okyungjin.py new file mode 100644 index 0000000000..92991d53af --- /dev/null +++ b/contains-duplicate/okyungjin.py @@ -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 diff --git a/house-robber/okyungjin.py b/house-robber/okyungjin.py new file mode 100644 index 0000000000..22e65a2e33 --- /dev/null +++ b/house-robber/okyungjin.py @@ -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 + + # 순회 끝나면 직전 최대 수익이 prev1에 저장됨 + return prev1 + diff --git a/longest-consecutive-sequence/okyungjin.py b/longest-consecutive-sequence/okyungjin.py new file mode 100644 index 0000000000..456921e0c8 --- /dev/null +++ b/longest-consecutive-sequence/okyungjin.py @@ -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([]) diff --git a/top-k-frequent-elements/okyungjin.py b/top-k-frequent-elements/okyungjin.py new file mode 100644 index 0000000000..efb9e38ec4 --- /dev/null +++ b/top-k-frequent-elements/okyungjin.py @@ -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] diff --git a/two-sum/okyungjin.py b/two-sum/okyungjin.py new file mode 100644 index 0000000000..de4864efbf --- /dev/null +++ b/two-sum/okyungjin.py @@ -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