백준 2108. 통계학 (+ 소수점 반올림 관련)
2108번: 통계학
첫째 줄에 수의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 그 다음 N개의 줄에는 정수들이 주어진다. 입력되는 정수의 절댓값은 4,000을 넘지 않는다.
www.acmicpc.net
문제제목 : 통계학
문제난이도 : 실버4
문제유형 : 정렬, 구현
문제
수를 처리하는 것은 통계학에서 상당히 중요한 일이다. 통계학에서 N개의 수를 대표하는 기본 통계값에는 다음과 같은 것들이 있다. 단, N은 홀수라고 가정하자.
- 산술평균 : N개의 수들의 합을 N으로 나눈 값
- 중앙값 : N개의 수들을 증가하는 순서로 나열했을 경우 그 중앙에 위치하는 값
- 최빈값 : N개의 수들 중 가장 많이 나타나는 값
- 범위 : N개의 수들 중 최댓값과 최솟값의 차이
N개의 수가 주어졌을 때, 네 가지 기본 통계값을 구하는 프로그램을 작성하시오.
입력
첫째 줄에 수의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 그 다음 N개의 줄에는 정수들이 주어진다. 입력되는 정수의 절댓값은 4,000을 넘지 않는다.
출력
첫째 줄에는 산술평균을 출력한다. 소수점 이하 첫째 자리에서 반올림한 값을 출력한다.
둘째 줄에는 중앙값을 출력한다.
셋째 줄에는 최빈값을 출력한다. 여러 개 있을 때에는 최빈값 중 두 번째로 작은 값을 출력한다.
넷째 줄에는 범위를 출력한다.
코드
package week2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class BOJ2108 {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
int n = Integer.parseInt(br.readLine());
int[] arr = new int[8001]; // 입력값 범위 : -4000 ~ 4000
int sum = 0; // 총 합
int max = Integer.MIN_VALUE; // 최대값
int min = Integer.MAX_VALUE; // 최소값
int mid = 10000;
int freq = 10000;
for (int i = 0; i < n; i++) {
int input = Integer.parseInt(br.readLine());
//총 합 얻기
sum += input;
arr[input + 4000]++;
//최대값 찾기
if (max < input) {
max = input;
}
//최소값 찾기
if (min > input) {
min = input;
}
}
int count = 0;
int freq_max = 0; // 최빈값의 최대값
// 이전의 동일한 최빈값이 1번만 등장했을경우 true, 아닐경우 false
boolean flag = false;
for (int i = min + 4000; i <= max + 4000; i++) { //-5을 입력한 경우 3995에 들어가있음.
if (arr[i] > 0) { // 해당 값이 입력되어진 경우
//중앙값 찾기
if (count <= n / 2) {
count += arr[i]; // 해당 값의 빈도수를 count에 합산
mid = i - 4000;
}
// 최빈값 찾기
if (freq_max < arr[i]) {
freq_max = arr[i];
freq = i - 4000;
flag = true; // 첫번쨰 최빈값을 찾았으니 true 로 변경, 이후 중복된 빈도수를 갖는 값을 만나면 그 값이 두번째로 작은 값이므로 그 값이 출력되어야함.
}
// 최대 최빈값과 빈도수가 같고, 앞에서 최대 최대값을 찾은 경우.
else if (freq_max == arr[i] && flag == true) {
freq = i - 4000;
flag = false;
}
}
}
bw.write(String.format("%.0f", (double) sum / (double) n) + "\n");
bw.write(String.valueOf(mid) + "\n"); // 중앙값
bw.write(String.valueOf(freq) + "\n"); // 최빈값
bw.write(String.valueOf(max - min) + "\n"); // 범위
bw.flush();
bw.close();
}
}
아이디어
하... 최빈값 구하기 진짜 어렵다... 그냥 최빈값을 구하는건 정말 쉽게 나오는데 하필 최빈값이 같은 값이 있다면 두번째로 작은 값을 출력해야하니까 많이 헷갈렸다.
이 문제는 어떤 문제인가?
- 숫자를 받아 산술평균, 중앙값, 최빈값(중복 시, 두번째로 작은 값 출력), 범위(최대-최소)를 구하는 문제이다.
어떻게 풀었는가?
일단
1. 산술평균
- 산술평균은 입력받은 값을 모두 합한 뒤 n으로 나누면 쉽게 평균을 구할 수 있다. 따라서 값을 입력받을때, 배열에 넣으면서 sum변수를 이용해 총 합을 구하였다.
2. 중앙값
- 중앙값은 정렬된 상태에서 중앙(n/2)번째에 있는 값이다. 중앙값을 구하기 위한 방법은 카운팅정렬을 사용하는 방법, Arrays.sort()를 사용하는 방법이 있다. Arrays.sort()는 시간복잡도가 O(nlogn)이고 카운팅정렬은 O(n)이므로 사실 카운팅 정렬이 더 효과적이라고 볼 수 있다.
3. 최빈값
- 카운팅 정렬을 이용하여 정렬을 전체 스캔하면서 가장 높은 빈도수를 갖는 값을 출력할 수 있고, 중복이 있는 경우 두번째로 작은 값을 출력해야하므로 이를 체크할 수 있는 변수 하나를 추가해서 작성하였다.
4. 범위
- 범위는 최대값과 최소값을 구하여 둘을 빼면 되는 것으로 값을 입력받을때, 조건문을 활용하여 최대값과 최소값을 구해주었다.
추가학습
자바 소수점 n번자리까지 반올림하기 방법
1. Math.round()
double pie = 3.14159265358979;
System.out.println(Math.round(pie)); //결과 : 3
System.out.println(Math.round(pie*100)/100.0); //결과 : 3.14
System.out.println(Math.round(pie*1000)/1000.0); //결과 : 3.142
2. String.format()
double pie = 3.14159265358979;
double money = 4424.243423;
System.out.println(String.format("%.2f", pie)); //결과 : 3.14
System.out.println(String.format("%.3f", pie)); //결과 : 3.142
System.out.println(String.format("%,.3f", money)); //결과 : 4,424.243
Math.round()와 String.format()의 차이점
- Math.round()함수는 소수점 아래가 0일 경우 정수부분만 출력한다.
- String.format()의 경우, 50.000 등 소수점이 함께 출력된다.