Ch.3 해시맵과 해시셋

빈도수 세기 패턴 — Counter의 원리

dict와 Counter/defaultdict로 빈도수를 세는 3가지 방법을 구현한다빈도수 기반 문제에서 해시맵 패턴을 적용한다

문자열에서 가장 많이 나온 글자, 어떻게 찾을까요?

빈도수 세기는 코딩 면접에서 가장 자주 나오는 해시맵 패턴입니다. dict, defaultdict, Counter — 세 가지 방법의 차이를 알아봅시다.

매번 if문으로 키 존재 여부를 확인해야 할까?

Counter와 defaultdict가 그 번거로움을 없애줍니다. 가장 Pythonic한 방법을 배워봅시다.

lightbulb

핵심 개념

빈도수 세기

각 원소가 몇 번 등장하는지 해시맵으로 카운팅

Counter

collections 모듈의 빈도수 전용 딕셔너리


article

핵심 내용

가장 기본적인 방법: 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")) # True

Counter 비교는 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]))  # 2

3가지 방법 비교: • 기본 dict → if-else 필요, 코드 길어짐 • defaultdict(int) → 깔끔하지만 most_common 없음 • Counter → 한 줄로 해결, most_common 제공

Counter('aabbbc')에서 most_common(1)의 결과는?

Counter("abc")["z"]를 실행하면 KeyError가 발생한다

빈도수 마스터!

key

핵심 용어

🔢

빈도수 세기

각 원소의 등장 횟수를 해시맵으로 카운팅

📊

Counter

collections 모듈의 빈도수 전용 딕셔너리

edit_note

정리 노트

빈도수 세기 — 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를 떠올리세요!

image

시각 자료

다이어그램: ds-d23
check_circle

핵심 정리

  • 1dict → defaultdict → Counter 순으로 편리해짐
  • 2Counter는 most_common, 집합 연산까지 지원
  • 3아나그램, 다수 원소 등 빈도수 패턴의 핵심 도구

퀴즈와 인터랙션으로 더 깊이 학습하세요

play_circle인터랙티브 레슨 시작