[Java] 백준(실버-3) 2108번 - 통계학

입출력 예시
Input-1
5 1 3 8 -2 2
Output-1
2 2 1 10
Input-2
5 -1 -2 -3 -1 -2
Output-2
-2 -2 -1 2
문제 풀이
N개의 수들을 입력받아 산술평균, 중앙값, 최빈값, 범위 총 4가지를 구해야한다.
위 4가지 연산결과를 구하기 위해 N개의 수를 배열에 저장해두자.
유의할 점은 N개의 수 중 최빈값이 동일한 수가 있다면 두번째로 작은 수를 출력해야 한다는 것이다.
4가지 연산을 위해 생각한 아이디어는 다음과 같다.
- 산술평균: 배열 원소의 누적 합을 구한다.
- 중앙값: N은 무조건 홀수이기 때문에 배열[배열의 길이/2]로 접근하면 중앙값을 구할 수 있다.
- 최빈값: N개의 수들의 각 빈도를 체크해두고, 가장 많이 나온 수가 하나일 경우 하나만 출력하고, 빈도수가 동일한 수가 있다면 빈도순으로 나열하여 두번째로 작은 값을 출력한다.
- 범위: 배열을 오름차순으로 정렬한 후, 첫번째 원소는 최솟값, 마지막 원소는 최대값일테니, 마지막원소-첫번째원소 연산을 통해 범위를 구한다.
그럼 도출한 아이디어를 가지고 코드를 작성해보자.
1
2
3
4
5
6
7
int N = Integer.parseInt(st.nextToken());
int[] arr = new int[N];
for(int i=0; i<arr.length; i++) {
arr[i] = Integer.parseInt(br.readLine());
}
Arrays.sort(arr);
먼저 N을 입력받고 N개의 수들을 담을 arr 배열을 선언한다. 그리고 N개만큼 입력을 받아 arr 배열에 저장한다.
다음으로 arr 배열을 오름차순으로 정렬한다.
산술평균 구하기
산술평균의 경우는 먼저 배열 원소를 순회하며 누적합을 구한 뒤 누적합에서 N을 나누면 되는데,
그냥 나누면 소수점이 버려지기 때문에 반올림을 위해 Math.round를 이용해야 한다.
하지만, Math.round가 동작하기 전 누적합/N 연산에서 오차가 발생할 수 있기 때문에 누적합/N 연산 부분을 double형으로 형변환하자. 그렇다면 소수점이 버려지는 것을 방지할 수 있다.
마지막으로 반올림된 연산에 int형으로 형변환하면 된다.
1
2
3
4
5
int sum = 0;
for(int i=0; i<arr.length; i++) {
sum += arr[i];
}
bw.write((int)Math.round((double)sum/N)+"\n");
중앙갑 구하기
1
bw.write(arr[arr.length/2]+"\n");
N이 홀수이기에 중앙값은 항상 존재하고, 중앙값은 단순하게 배열의 길이를 반으로 나눈 인덱스 값이 된다.
최빈값 구하기
최빈값은 배열의 원소중 가장 많이 나온 수이다. 또한 최빈값을 가진 수가 여러개 존재할 경우는 두번 째로 작은 수를 출력해야 하기 때문에 TreeMap을 활용하였다.
TreeMap 이란?
이진트리를 기반으로 한 Map 컬렉션이다. TreeMap은 기본적으로 값들을 key 값 기준으로 오름차순으로 정렬하여 가지고 있다.
TreeMap은 값을 저장하면 자동으로 오름차순 정렬되기에 동일한 최빈값을 가진 수가 있을 때 두번째로 작은 수를 찾기가 쉽기에 이용하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TreeMap<Integer, Integer> hm = new TreeMap<>();
for(int i=0; i<arr.length; i++) {
hm.put(arr[i], hm.getOrDefault(arr[i], 0)+1);
}
int max = Collections.max(hm.values());
ArrayList<Integer> mode_list = new ArrayList<>();
for(Map.Entry<Integer, Integer> entry : hm.entrySet()) {
if(max == entry.getValue()) mode_list.add(entry.getKey());
}
int mode = 0;
if(mode_list.size() == 1) mode = mode_list.get(0);
else mode = mode_list.get(1);
N개의 수가 담긴 arr 배열에서 원소를 key값으로, 원소의 빈도수를 value 값으로 하여 TreeMap에 저장한다. 이 때 TreeMap에는 key 값 기준으로 오름차순 정렬이 된다.
그리고 가장 많은 빈도횟수 max 변수를 가지고 TreeMap을 순회하며 가장 많은 빈도수를 가지는 수를 mode_list 배열에 담는다.
최빈값이 하나라면 mode_list의 원소는 하나이며, 최빈값이 X개라면 mode_list의 원소는 X개 일 것이다.
최빈값이 몇개가 담기든 TreeMap이기에 오름차순으로 mode_list에 담길 테니 두번째로 작은 수를 출력하면 된다.
범위 구하기
1
bw.write((arr[arr.length-1] - arr[0])+"\n");
범위를 구하는 것은 단순하다. arr 배열을 앞에서 sort() 함수를 통해 오름차순 정렬했으니 최대값(마지막원소)-최솟값(첫번째원소) 연산을 출력하면 된다.
작성코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.io.*;
import java.util.*;
class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int[] arr = new int[N];
for(int i=0; i<arr.length; i++) {
arr[i] = Integer.parseInt(br.readLine());
}
Arrays.sort(arr);
TreeMap<Integer, Integer> hm = new TreeMap<>();
int sum = 0;
for(int i=0; i<arr.length; i++) {
sum += arr[i];
hm.put(arr[i], hm.getOrDefault(arr[i], 0)+1);
}
int max = Collections.max(hm.values());
ArrayList<Integer> mode_list = new ArrayList<>();
for(Map.Entry<Integer, Integer> entry : hm.entrySet()) {
if(max == entry.getValue()) mode_list.add(entry.getKey());
}
int mode = 0;
if(mode_list.size() == 1) mode = mode_list.get(0);
else mode = mode_list.get(1);
bw.write((int)Math.round((double)sum/N)+"\n");
bw.write(arr[arr.length/2]+"\n");
bw.write(mode+"\n");
bw.write((arr[arr.length-1] - arr[0])+"\n");
bw.flush();
bw.close();
br.close();
}
}
회고
- TreeMap에 저장하면 자동으로 오름차순으로 정렬되는 성질을 이용해 최빈값과 두번째 최빈값를 구할 수 있었다.