[백준 문제 풀이] 2108번 통계학

Junu Kim·2026년 1월 17일
post-thumbnail

[2108] 통계학

난이도: ★★☆☆☆ • solved on: 2026-01-17


문제 요약

  • 문제 유형: 정렬, 구현, 해시/카운팅
  • 요구사항: N개의 정수에 대해 산술평균(반올림), 중앙값, 최빈값, 범위를 출력해야 한다.

사용 개념

  1. 자료구조

    • 방법 1: int[], HashMap<Integer,Integer>, ArrayList<Integer>
    • 방법 2: int[] count (8001) 카운팅 배열
  2. 알고리즘/기법

    • 방법 1: 정렬 + 해시맵 빈도
    • 방법 2: 카운팅 (Counting) + 누적합 스캔
  3. 핵심 키워드

    • 반올림 (rounding)
    • 최빈값 tie-breaking (동률 시 두 번째로 작은 값)
    • 값 범위 고정 (-4000~4000)

풀이 아이디어 및 코드

방법 1 : HashMap + 정렬

  1. 문제 분해
  • 입력을 받으면서 min, max, total을 갱신한다.
  • HashMap으로 각 수의 빈도를 센다.
  • 최빈값 후보를 ArrayList에 모은 뒤 정렬로 규칙(두 번째로 작은 값)을 맞춘다.
  • arr를 정렬해 중앙값을 구한다.
  1. 핵심 로직 흐름

    입력:
        min/max/total 갱신
        map[num]++
    
    map 순회:
        최대 빈도 cnt 갱신
        cnt와 같으면 후보 추가
        cnt보다 크면 후보 초기화 후 추가
    
    arr 정렬 -> 중앙값
    후보 정렬 -> 최빈값 결정(두 번째로 작은 값)
  2. 예외 처리

    • 평균은 Math.round(1.0 * total / n) 형태로 반올림한다.
import java.util.*; 
import java.lang.*;
import java.io.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n =  Integer.parseInt(br.readLine());

        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        int total = 0;
        int freq = 4001;
        int cnt = 0;
        ArrayList<Integer> arrayList = new ArrayList<>();
        HashMap<Integer,Integer> map = new HashMap<>();
        int[] arr = new int[n];
        map.put(freq, -1);

        for(int i = 0; i < n; i++){
            int temp =  Integer.parseInt(br.readLine());
            if(temp < min) min = temp;
            if(temp > max) max = temp;

            total += temp;

            if(map.containsKey(temp)){
                map.put(temp,map.get(temp)+1);
            } else {
                map.put(temp,1);
            }
            arr[i] = temp;
        }
        for(Integer key : map.keySet()){
            if(map.get(key).equals(-1)){
                continue;
            }
            if(map.get(key).equals(cnt)){
                arrayList.add(key);
            }
            if(map.get(key).compareTo(cnt)>0){
                arrayList.clear();
                arrayList.add(key);
                cnt = map.get(key);
            }
        }
        Arrays.sort(arr);
        arrayList.sort(Collections.reverseOrder());
        System.out.println(Math.round(1.0 * total / n));
        System.out.println(arr[n/2]);
        if(arrayList.size() == 1){
            System.out.println(arrayList.get(0));
        } else {
            System.out.println(arrayList.get(arrayList.size()-2));
        }
        System.out.println(max-min);
    }
}

방법 2 : 카운팅 배열로 동시 처리

  1. 문제 분해
  • 값 범위가 -4000 ~ 4000으로 고정이므로 count[8001]로 빈도를 센다(인덱스는 x + 4000).
  • 평균: sum / nMath.round로 반올림한다.
  • 중앙값: 누적 빈도가 (n+1)/2 이상이 되는 지점을 찾는다(문제에서 N은 홀수).
  • 최빈값: 최대 빈도를 찾은 뒤, 오름차순 스캔 중 최빈값을 만나는 두 번째 값을 답으로 한다(동률 규칙 충족).
  • 범위: max - min을 출력한다.
  1. 핵심 로직 흐름

    입력:
        sum, min, max 갱신
        count[x+4000]++
    
    median:
        누적합 >= (n+1)/2 되는 최초 값
    
    mode:
        maxFreq 구함
        i=0..8000 오름차순으로 스캔하며
            count[i]==maxFreq 인 값을 발견할 때마다 found++
            found==1이면 mode=값
            found==2이면 mode=값(두 번째로 작은 값)로 확정
  2. 예외 처리

    • 평균 반올림은 음수도 포함하므로 Math.round((double)sum / n)으로 처리한다.
import java.io.*;

class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());

        int[] count = new int[8001]; // -4000~4000 => +4000 shift
        long sum = 0;

        int min = 4001;
        int max = -4001;

        for (int i = 0; i < n; i++) {
            int x = Integer.parseInt(br.readLine());
            sum += x;
            if (x < min) min = x;
            if (x > max) max = x;
            count[x + 4000]++;
        }

        // 1) 산술평균
        int mean = (int) Math.round((double) sum / n);

        // 2) 중앙값 (N은 홀수)
        int target = (n + 1) / 2;
        int cumulative = 0;
        int median = 0;
        for (int i = 0; i < 8001; i++) {
            cumulative += count[i];
            if (cumulative >= target) {
                median = i - 4000;
                break;
            }
        }

        // 3) 최빈값 (여러 개면 두 번째로 작은 값)
        int maxFreq = 0;
        for (int i = 0; i < 8001; i++) {
            if (count[i] > maxFreq) maxFreq = count[i];
        }

        int mode = 0;
        int found = 0;
        for (int i = 0; i < 8001; i++) {
            if (count[i] == maxFreq) {
                mode = i - 4000;
                found++;
                if (found == 2) break; // 두 번째로 작은 최빈값 확정
            }
        }

        // 4) 범위
        int range = max - min;

        StringBuilder sb = new StringBuilder();
        sb.append(mean).append('\n');
        sb.append(median).append('\n');
        sb.append(mode).append('\n');
        sb.append(range).append('\n');
        System.out.print(sb);
    }
}

시간·공간 복잡도

방법 1

  • 시간 복잡도: O(N log N) (정렬)
  • 공간 복잡도: O(N) (배열 + 맵/리스트)

방법 2

  • 시간 복잡도: O(N + K) (K=8001 고정) → 사실상 O(N)
  • 공간 복잡도: O(K) → 사실상 O(1)

어려웠던 점

  • 최빈값을 어떻게 저장하고 찾아낼지에 대해 골머리를 앓았다 처음 접근 (방법1)에서는 따로 HashMap을 저장하여 cnt를 확인했는데 나중 접근을 통해 카운팅 배열을 활용해볼 수 있다는 걸 확인했다.

배운 점 및 팁

  • 값의 범위가 작고 고정이면 HashMap정렬보다 카운팅 배열이 구현도 단순하고 성능도 안정적이다.
  • 최빈값 동률 규칙은 정렬 후 두 번째를 요구하므로, 오름차순 스캔 중 두 번째로 만나는 최빈값을 고르는 방식이 가장 안전하다. (시간 복잡도는 증가하나, 공간복잡도는 간결하다)
  • 평균 반올림은 음수도 포함하므로 Math.round((double)sum / n)처럼 double로 변환해 처리하는 편이 명확하다.

참고 및 링크


추가 연습 문제

profile
생각이 현실이 될 수 있도록 노력하는 중입니다.

0개의 댓글