문제
수를 처리하는 것은 통계학에서 상당히 중요한 일이다. 통계학에서 N개의 수를 대표하는 기본 통계값에는 다음과 같은 것들이 있다. 단, N은 홀수라고 가정하자.
산술평균 : N개의 수들의 합을 N으로 나눈 값
중앙값 : N개의 수들을 증가하는 순서로 나열했을 경우 그 중앙에 위치하는 값
최빈값 : N개의 수들 중 가장 많이 나타나는 값
범위 : N개의 수들 중 최댓값과 최솟값의 차이
N개의 수가 주어졌을 때, 네 가지 기본 통계값을 구하는 프로그램을 작성하시
입력
첫째 줄에 수의 개수 N (1 <= N <= 500,000)
둘째 줄부터 N개의 줄까지 정수(절댓값 4,000 이하)
출력
첫째 줄에 산술평균(소수점 이하 첫째 자리에서 반올림한 값)
둘째 줄에 중앙값
셋째 줄에는 최빈값(최빈값이 여러 개일 경우 두 번째로 작은 값)
넷째 줄에는 최댓값-최솟값
뭔가 깔끔하게 풀고 싶었는데 딱히 방법이 생각나지 않았다...
그래서
1. 산술평균
처음에 input 받으면서 값을 전부 더함
이후 N으로 나눔, Math.round() 써서 소수점 처리
HashMap을 사용해서 key에는 정수, value에는 정수가 나온 개수를 저장
2. 중앙값
오름차순으로 정렬된 map.keySet()을 순회하며
map에 저장된 value 값을 참고하여 N/2 중앙값에 해당하는 값을 찾음
최빈값
가장 많이 나온 값이므로 map.keySet() 순회하며 제일 value가 큰 값을 뽑음
여러 개일 경우 처리가 필요하므로 ArrayList에 등록함
만약 list.get(0)보다 값이 큰 경우 list 비우고 새롭게 추가
동일한 경우 그냥 추가
최댓값 - 최솟값
정렬한 map.keySet()의 맨 끝 인덱스, 0번 인덱스를 가져와 뺌
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 1. 평균 (소수 첫 째 자리에서 반올림한 값)
// 2. 중앙값
// 3. 최빈값 (두 번째로 작은 값)
// 4. 최댓값 - 최솟값
int N = Integer.parseInt(br.readLine());
double totalSum = 0;
HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i = 0; i<N; i++) {
int num = Integer.parseInt(br.readLine());
totalSum += num;
map.put(num, map.getOrDefault(num, 0)+1);
}
// 평균(소수 첫 번재 자리에서 반올림)
bw.write(Math.round(totalSum/N) + "\n");
// 중앙값
Integer[] keys = map.keySet().toArray(new Integer[map.size()]);
Arrays.sort(keys);
int pivotIndex = -1;
int medianIndex = 0;
if(map.size() > 1) {
while(medianIndex <= N/2) {
pivotIndex++;
medianIndex += map.get(keys[pivotIndex]);
}
bw.write(keys[pivotIndex] + "\n");
} else bw.write(keys[0] + "\n");
// 최빈값(여러 개 있으면 두 번째로 작은 값)
ArrayList<Integer> mode = new ArrayList<>();
mode.add(keys[0]);
for(int i=1; i<keys.length; i++) {
int currMax = map.get(mode.get(0)); // 기본 비교값
if(map.get(keys[i]) > currMax) { // 만약 가져온 값의 value가 현재 max 값보다 크면 비우고 바꿈
mode.clear();
mode.add(keys[i]);
} else if(map.get(keys[i]) == currMax) { // 동일하면 추가
mode.add(keys[i]);
} else continue;
}
if(mode.size() > 1) {
Collections.sort(mode);
bw.write(mode.get(1) + "\n");
} else {
bw.write(mode.get(0) + "\n");
}
// 최댓값 - 최솟값
bw.write(keys[keys.length-1] - keys[0] + "\n");
bw.flush();
bw.close();
br.close();
}
}
이렇게 더럽게 풀어도 되는 걸까.. Counting sort를 사용하는 방법도 생각하긴 했는데... 중앙값까지는 Counting Sort를 통해 푸는 방법이 생각 났는데 최빈값을 어떻게 Counting Sort로 풀어낼 지에 대한 방법이 생각나지 않아 떠오르는 대로 구현하였다.
처음에 평균을 구할 때 float을 사용하여 틀렸었다. 정확한 값을 사용하기 위해서는 BigDecimal을 써야 되는 건 알고 있었는데... BigDecimal이 아닌 Double로만 변경해도 맞았다고 떴다.
문제를 푸는 김에 겸사겸사 Float, Double, BigDecimal의 차이도 확인해보았다.
부동 소수점 표현
소수의 자리 수가 굉장히 큰 값(혹은 무한 소수)를 저장하게 되는 경우, 모두 저장할 수 없기에 일부를 잘라서 저장하게 되고, 오차가 생기게 됨
Float은 4Byte, Double은 8Byte
Double이 Float에 비해 더 정밀한 값을 표현할 수 있음
부동 소수점의 한계를 극복하기 위해 설계된 자바 라이브러리의 클래스
자료형의 범위가 넓고 유효 숫자, 소수점을 기준으로 표현
불변 객체라 메모리를 많이 소요 (그리고 사용도 불편!)