Skip to content
30 changes: 30 additions & 0 deletions contains-duplicate/okyungjin.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, Two Pointers
  • 설명: 정렬 후 인접 중복을 확인하는 방식은 Two Pointers의 변형으로 간주 가능하며, 두 번째 구현은 해시 집합(Hash Set)으로 중복 탐지를 수행한다. 두 가지 모두 중복 여부를 빠르게 판단하는 패턴이다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(nlogN) O(n)
Space O(nlogN) O(1)

피드백: 정렬 풀이와 해시셋 풀이 두 가지가 제시되어 있습니다. 두 방법 모두 한 번의 선형 스캔으로 중복 여부를 확인합니다.

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

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.

두 풀이가 함께 있어서 가독성 비교하기 좋았던 거 같습니다!

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
31 changes: 31 additions & 0 deletions house-robber/okyungjin.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 패턴을 사용합니다. 또한 선택과 비교로 최댓값을 갱신하는 방식은 그리디 성격도 보이며, 공간을 O(1)로 유지합니다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(N) O(n)
Space O(1) O(1)

피드백: 연속 여부를 판단하며 두 이전 상태만 유지하는 최적화된 동적계획법 구현입니다.

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

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

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.

Suggested change
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
prev2 = 0 # 앞앞집 최대 수익
prev1 = 0 # 앞집 최대 수익
for num in nums:
# 현재 최대 수익
curr = max(prev1, prev2 + num)
prev2 = prev1
prev1 = curr

로직이 직관적이어서 좋네요! prev1, prev2에 대한 주석은 선언 시점에만 있어도 잘 전달되는 것 같고 슬라이딩 관련된 로직은 주석이 없어도 충분히 의미가 전달되는 거 같습니다 :)


# 순회 끝나면 직전 최대 수익이 prev1에 저장됨
return prev1

52 changes: 52 additions & 0 deletions longest-consecutive-sequence/okyungjin.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.

🏷️ 알고리즘 패턴 분석

  • 패턴: Greedy, Dynamic Programming, Sort
  • 설명: 정렬과 연속 여부 판단으로 최댓값을 갱신하는 방식으로 문제를 해결한다. 시간 복잡도 목표는 O(N)이나 구현은 정렬(O(N log N))를 사용하고, 연속 부분 문자열처럼 길이를 추적하며 최댓값을 업데이트하는 아이디어가 Greedy 성격에 가깝다.

📊 시간/공간 복잡도 분석

유저 분석 실제 분석 결과
Time O(n log n) O(n log n)
Space O(N) O(1)

피드백: 정렬 후 순회로 연속 부분 수열의 길이를 계산합니다. 중복 처리도 포함되어 있습니다.

개선 제안: 가능하면 해시셋 기반의 선형 해법으로 개선 여지가 있습니다.

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([])
117 changes: 117 additions & 0 deletions top-k-frequent-elements/okyungjin.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, Heap / Priority Queue, Greedy, Dynamic Programming
  • 설명: 코드에서 빈도수를 세기 위해 해시 맵(Counter/dict)을 사용하고, 상위 k개를 뽑기 위해 최소힙(heapq) 등 여러 방법으로 정렬/선정하는 구조를 보인다. 부분적으로 최적해 도출과 부분집합 추출이 핵심이며, 빈도 기반의 선택은 그리디적 요소도 포함한다.

📊 시간/공간 복잡도 분석

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

풀이 1: SolutionA.topKFrequent — Time: ✅ O(nlogK) → O(n log k) / Space: ✅ O(N) → O(n)
유저 분석 실제 분석 결과
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)

피드백: 메모리 대비 속도 면에서 균형 있는 방법입니다.

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

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.

다양한 풀이 시도해보신 것 멋지네요! 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]
28 changes: 28 additions & 0 deletions two-sum/okyungjin.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, Greedy
  • 설명: 해당 코드는 한 번의 해를 찾기 위해 숫자와 인덱스를 해시 맵에 저장하고, 각 원소에 대해 필요한 페어를 빠르게 조회하는 방식으로 동작한다. 이를 통해 선형 시간에 문제를 해결한다.

📊 시간/공간 복잡도 분석

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

피드백: 해시맵 활용으로 선형 시간에 해결하는 표준 풀이입니다.

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

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

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
Loading