Algorithm/Baekjoon

[Python | 2910] 빈도 정렬

dotz0ver 2025. 3. 20. 15:29

https://www.acmicpc.net/problem/2910


문제 설명

위대한 해커 창영이는 모든 암호를 깨는 방법을 발견했다. 그 방법은 빈도를 조사하는 것이다.

창영이는 말할 수 없는 방법을 이용해서 현우가 강산이에게 보내는 메시지를 획득했다. 이 메시지는 숫자 N개로 이루어진 수열이고, 숫자는 모두 C보다 작거나 같다. 창영이는 이 숫자를 자주 등장하는 빈도순대로 정렬하려고 한다.

만약, 수열의 두 수 X와 Y가 있을 때, X가 Y보다 수열에서 많이 등장하는 경우에는 X가 Y보다 앞에 있어야 한다. 만약, 등장하는 횟수가 같다면, 먼저 나온 것이 앞에 있어야 한다.

이렇게 정렬하는 방법을 빈도 정렬이라고 한다.

수열이 주어졌을 때, 빈도 정렬을 하는 프로그램을 작성하시오.

 

📌 시간 제한: 1


입력

첫째 줄에 메시지의 길이 N과 C가 주어진다. (1 ≤ N ≤ 1,000, 1 ≤ C ≤ 1,000,000,000)

둘째 줄에 메시지 수열이 주어진다.


출력

첫째 줄에 입력으로 주어진 수열을 빈도 정렬한 다음 출력한다.


문제 접근

이 문제는 주어진 숫자들을 빈도수 기준으로 정렬하는 문제다. 단, 빈도수가 같을 경우에는 입력에서 먼저 등장한 숫자가 우선순위를 갖도록 정렬해야 한다.

 

이 문제를 해결하기 위해 두 가지 핵심 조건을 고려해야 한다.

  1. 빈도수가 높은 숫자일수록 먼저 출력해야 한다.
  2. 빈도수가 같다면, 입력에서 먼저 등장한 숫자가 앞에 와야 한다.
    (즉, 입력 순서를 유지해야 함)

이를 위해, 다음과 같은 방법을 사용했다.

  • 숫자들의 등장 횟수를 저장하기 위해 Counter를 활용.
  • 각 숫자가 처음 등장한 위치를 저장하여 정렬 시 활용.
  • 빈도수와 등장 순서를 기준으로 정렬 후 결과를 출력.
이 코드의 시간 복잡도를 분석해보면, 먼저 Counter를 사용하여 숫자들의 빈도수를 계산하는 과정이 O(N)에 해당한다. 그다음, enumerate를 이용해 각 숫자의 첫 등장 위치를 기록하는 과정도 O(N)이 걸린다.

가장 시간이 많이 걸리는 부분은 정렬 과정인데, sorted() 함수를 사용할 때 기본적으로 O(N log N)의 시간 복잡도를 가진다. 마지막으로, 정렬된 숫자를 빈도수만큼 리스트에 추가하는 과정은 O(N)이므로 전체적으로 O(N log N)이 최종 시간 복잡도가 된다.

이 문제의 최대 입력 크기가 1000이므로, O(N log N)은 최악의 경우에도 약 10,000번의 연산이 필요해 충분히 빠르게 실행될 수 있다.

 

예제로 코드가 어떻게 동작하는지는 다음과 같다.

 

1️⃣ 입력값

9 3
1 3 3 3 2 2 2 1 1

 

 

2️⃣ 숫자의 빈도수 계산

Counter(nums)를 이용하여 숫자별 등장 횟수를 구하면 다음과 같다.

숫자 빈도수
1 3
3 3
2 3

 

 

3️⃣ 숫자의 첫 등장 위치 저장

order = {num: idx for idx, num in enumerate(nums)}

 

각 숫자가 처음 등장한 위치를 기록한다.

숫자 첫 등장 위치 (인덱스)
1 0
3 1
2 4

 

 

4️⃣ 정렬 기준 적용

정렬 기준:

  • 빈도수 내림차순 → -counter[x]
  • 빈도수가 같다면, 먼저 등장한 순서대로 정렬 → order[x]
숫자 빈도수 첫 등장 위치 정렬 기준 (-빈도수, 등장 순서)
1 3 0 (-3, 0)
3 3 1 (-3, 1)
3 4 (-3, 4)

정렬된 순서: [1, 3, 2]
(빈도수가 같으므로, 먼저 등장한 1이 가장 먼저 오고, 다음으로 3, 그리고 2)

 

 

5️⃣ 최종 결과 리스트 생성

정렬된 순서대로 숫자를 빈도수만큼 추가한다.

숫자 추가되는 개수 리스트 상태
1 3개 [1, 1, 1]
3 3개 [1, 1, 1, 3, 3, 3]
2 3개 [1, 1, 1, 3, 3, 3, 2, 2, 2]

구현

처음 구현 (실패)

  • 입력 처리
    sys.stdin.readline()을 사용해 입력을 받고, Counter를 활용해 숫자들의 등장 횟수를 저장한다.
    또한, {num: idx for idx, num in enumerate(nums)}을 통해 각 숫자가 처음 등장한 위치를 저장한다.
  • 정렬 기준
    • 첫 번째 기준: 빈도수가 높은 숫자부터 정렬 (-counter[x] → 내림차순).
    • 두 번째 기준: 빈도수가 같으면, 먼저 등장한 숫자가 앞에 오도록 정렬 (order[x] → 오름차순).
  • 결과 생성 및 출력
    정렬된 숫자를 빈도수만큼 리스트에 추가한 후, print(*result)를 사용해 공백을 기준으로 출력한다.
import sys
from collections import Counter

# 입력 처리
n, c = map(int, sys.stdin.readline().split())
nums = list(map(int, sys.stdin.readline().split()))

# 숫자의 빈도수 계산
counter = Counter(nums)  # {숫자: 빈도수} 형태
order = {num: idx for idx, num in enumerate(nums)}  # 숫자의 첫 등장 위치 저장

# 정렬 기준: 1) 빈도수 내림차순 2) 등장 순서 오름차순
sorted_nums = sorted(counter.keys(), key=lambda x: (-counter[x], order[x]))

# 정렬된 숫자를 빈도수만큼 리스트에 추가
result = []
for num in sorted_nums:
    result.extend([num] * counter[num])  # 숫자를 빈도수만큼 반복

# 결과 출력
print(*result)

 

두 번째 구현 (성공)

기존 코드에서 order를 만들 때, order = {num: idx for idx, num in enumerate(nums)}을 사용했는데,
이 방식은 같은 숫자가 나중에 또 등장하면 이전의 등장 순서가 덮어씌워지는 문제가 발생할 가능성이 있었다.
따라서, 처음 등장한 숫자만 기록할 수 있도록 for 문을 사용하여 수정했다.

또한, 같은 빈도수를 가진 숫자들이 정렬될 때 등장 순서(order)가 제대로 반영되지 않는 문제가 있었다.
기존 코드에서는 sorted(counter.keys(), key=lambda x: (-counter[x], order[x]))가 정확히 동작하지 않을 수도 있었는데,
이제는 첫 등장 순서를 올바르게 저장한 후 정렬을 적용하여 문제를 해결했다.

import sys
from collections import Counter

# 입력 처리
n, c = map(int, sys.stdin.readline().split())
nums = list(map(int, sys.stdin.readline().split()))

# 숫자의 빈도수 계산
counter = Counter(nums)

# 숫자의 첫 등장 위치 저장 (덮어씌우기 방지)
order = {}
for idx, num in enumerate(nums):
    if num not in order:  # 처음 등장한 숫자만 저장
        order[num] = idx

# 정렬: 1) 빈도수 내림차순, 2) 같은 빈도수일 경우 등장 순서 오름차순
sorted_nums = sorted(counter.keys(), key=lambda x: (-counter[x], order[x]))

# 정렬된 숫자를 빈도수만큼 리스트에 추가
result = []
for num in sorted_nums:
    result.extend([num] * counter[num])  # 숫자를 빈도수만큼 반복

# 결과 출력
print(*result)

'Algorithm > Baekjoon' 카테고리의 다른 글

[Python | 11047] 동전 0  (0) 2025.03.28
[Python | 6198] 옥상 정원 꾸미기  (0) 2025.03.21
[Python | 6603] 로또  (0) 2025.03.11
[Python | 9020] 골드바흐의 추측  (0) 2025.03.08
[Python | 1182] 부분수열의 합  (0) 2025.03.07