Ch.3 해시맵과 해시셋
빈도수 세기 패턴 — Counter의 원리
문자열에서 가장 많이 나온 글자, 어떻게 찾을까요?
빈도수 세기는 코딩 면접에서 가장 자주 나오는 해시맵 패턴입니다. dict, defaultdict, Counter — 세 가지 방법의 차이를 알아봅시다.
매번 if문으로 키 존재 여부를 확인해야 할까?
Counter와 defaultdict가 그 번거로움을 없애줍니다. 가장 Pythonic한 방법을 배워봅시다.
핵심 개념
빈도수 세기
각 원소가 몇 번 등장하는지 해시맵으로 카운팅
Counter
collections 모듈의 빈도수 전용 딕셔너리
핵심 내용
가장 기본적인 방법: dict에 키가 있으면 +1, 없으면 1로 초기화
# 방법 1: 기본 dict
def count_chars_v1(s: str) -> dict:
freq = {}
for ch in s:
if ch in freq:
freq[ch] += 1
else:
freq[ch] = 1
return freq
print(count_chars_v1("banana"))
# {'b': 1, 'a': 3, 'n': 2}동작하지만... if-else가 번거롭습니다. 더 깔끔한 방법이 있을까요?
dict.get(key, 기본값) 으로 if-else를 한 줄로 줄일 수 있습니다
# 방법 2: dict.get()
def count_chars_v2(s: str) -> dict:
freq = {}
for ch in s:
freq[ch] = freq.get(ch, 0) + 1 # 없으면 0 + 1 = 1
return freq
# 방법 3: defaultdict
from collections import defaultdict
def count_chars_v3(s: str) -> dict:
freq = defaultdict(int) # 없는 키 접근 시 자동으로 0
for ch in s:
freq[ch] += 1 # KeyError 걱정 없음!
return dict(freq)
print(count_chars_v2("banana")) # {'b': 1, 'a': 3, 'n': 2}
print(count_chars_v3("banana")) # {'b': 1, 'a': 3, 'n': 2}defaultdict(int)는 존재하지 않는 키에 접근하면 자동으로 0을 반환합니다
Counter는 빈도수 세기에 특화된 딕셔너리입니다
from collections import Counter
# 한 줄로 빈도수 세기!
freq = Counter("banana")
print(freq) # Counter({'a': 3, 'n': 2, 'b': 1})
print(freq.most_common(2)) # [('a', 3), ('n', 2)] — 상위 2개
# 리스트에도 사용 가능
nums = [1, 1, 2, 3, 3, 3]
print(Counter(nums)) # Counter({3: 3, 1: 2, 2: 1})
# Counter끼리 연산도 가능!
a = Counter("aab")
b = Counter("abc")
print(a - b) # Counter({'a': 1}) ← a에만 더 있는 것
print(a & b) # Counter({'a': 1, 'b': 1}) ← 교집합Counter 핵심 기능: • Counter(iterable) — 한 줄로 빈도수 계산 • .most_common(k) — 상위 k개 반환 • Counter 간 +, -, &, | 연산 지원 • 존재하지 않는 키 → 0 반환 (KeyError 없음)
두 문자열이 아나그램인지 확인하는 문제. "listen"과 "silent"은 아나그램일까요?
from collections import Counter
def is_anagram(s: str, t: str) -> bool:
"""Counter로 빈도수 비교 — O(n)"""
return Counter(s) == Counter(t)
print(is_anagram("listen", "silent")) # True
print(is_anagram("hello", "world")) # False
print(is_anagram("anagram", "nagaram")) # TrueCounter 비교는 O(n)입니다. 정렬(O(n log n))보다 빠르죠!
배열에서 과반수 이상 등장하는 원소를 찾아라! (항상 존재한다고 가정)
from collections import Counter
def majority_element(nums: list[int]) -> int:
count = Counter(nums)
return count.most_common(1)[0][0]
# 테스트
print(majority_element([3, 2, 3])) # 3
print(majority_element([2, 2, 1, 1, 1, 2, 2])) # 23가지 방법 비교: • 기본 dict → if-else 필요, 코드 길어짐 • defaultdict(int) → 깔끔하지만 most_common 없음 • Counter → 한 줄로 해결, most_common 제공
Counter('aabbbc')에서 most_common(1)의 결과는?
Counter("abc")["z"]를 실행하면 KeyError가 발생한다
빈도수 마스터!
핵심 용어
빈도수 세기
각 원소의 등장 횟수를 해시맵으로 카운팅
Counter
collections 모듈의 빈도수 전용 딕셔너리
정리 노트
빈도수 세기 — Counter의 원리
3가지 방법
- dict + if
- 가장 기본 — if key in d: d[key] += 1
- defaultdict(int)
- 없는 키 → 자동 0 — KeyError 없음
- Counter
- 한 줄 빈도수 + most_common + 연산 지원
실전 활용
- 아나그램
- Counter(s) == Counter(t) — O(n)
- 다수 원소
- Counter(nums).most_common(1)
- Top K
- Counter(nums).most_common(k)
면접에서 빈도수 문제가 나오면 바로 Counter를 떠올리세요!
시각 자료
핵심 정리
- 1dict → defaultdict → Counter 순으로 편리해짐
- 2Counter는 most_common, 집합 연산까지 지원
- 3아나그램, 다수 원소 등 빈도수 패턴의 핵심 도구
퀴즈와 인터랙션으로 더 깊이 학습하세요
play_circle인터랙티브 레슨 시작