[백준 알고리즘] 평균은 넘겠지

Heejun Kwon·2021년 6월 11일
0

알고리즘 풀이

목록 보기
7/11
post-thumbnail

출처
백준 - 평균은 넘겠지



🔎 문제

대학생 새내기들의 90%는 자신이 반에서 평균은 넘는다고 생각한다.
당신은 그들에게 슬픈 진실을 알려줘야 한다.

🚫 입력 및 출력

<입력>
첫째 줄에는 테스트 케이스의 개수 C가 주어진다.

둘째 줄부터 각 테스트 케이스마다 학생의 수 N(1 ≤ N ≤ 1000, N은 정수)이 첫 수로 주어지고, 이어서 N명의 점수가 주어진다.
점수는 0보다 크거나 같고, 100보다 작거나 같은 정수이다.

<출력>
각 케이스마다 한 줄씩 평균을 넘는 학생들의 비율을 반올림하여 소수점 셋째 자리까지 출력한다.

💻 입출력 예

📄🤔 코드 및 풀이과정

🔹 1번

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Baekjoon4344 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		int c = Integer.parseInt(br.readLine());

		int[][] cases = new int[c][];
		int[] scoreArr = null;
		for (int i = 0; i < c; i++) {
			String[] input = br.readLine().split(" ");
			scoreArr = new int[input.length];
			for (int k = 0; k < input.length; k++) {
				scoreArr[k] = Integer.parseInt(input[k]);
			}
			cases[i] = scoreArr;
		}
		
        	int sum, count;
		double avg;

		for (int i = 0; i < c; i++) {
        		sum = 0; avg = 0; count = 0;
			for (int k = 1; k < cases[i].length; k++) {
				sum += cases[i][k];
			}
			avg = (double)sum / cases[i][0];
			for (int k = 1; k < cases[i].length; k++) {
				if(cases[i][k] > avg) count++;
			}
			System.out.printf("%.3f%%\n", (count / (double)cases[i][0] * 100));
			
		}
	}
}

🔸 1번 풀이

처음 코드는
입력받은 각 케이스들을 split하여 int형으로 변환 후
scoreArr 배열에 담은 뒤 이를
각 케이스의 점수들을 저장할 cases 배열에 담았다.

그리고 다음 for문에서 각 케이스들의 점수 합을 구하고, 평균을 구하고,
평균과 점수들을 비교해 평균보다 높은 점수들을 count했다.

이후 count와 케이스의 점수 갯수를 나눈 후 100을 곱해
문제에서 원하는 출력 결과대로 출력을 하였다.

이러한 로직으로 문제를 통과했으며
메모리 13584KB / 처리시간 144ms 가 나왔다.

통과는 했지만 여러모로 아쉬운 부분들이 많은 것 같아
코드를 수정해보기로 했다.


🔹 2번

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Baekjoon4344 {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = null;
        
		int c = Integer.parseInt(br.readLine());
		int[][] cases = new int[c][];
		int[] scoreArr = null;
	    	double[] avg = new double[c];
		double sum;
		int num, score, scoreCount; 
		
		for (int i = 0; i < c; i++) {
			st = new StringTokenizer(br.readLine());
			scoreCount = st.countTokens();
			scoreArr = new int[scoreCount];
			num = 0; sum = 0;
			while (st.hasMoreTokens()) {
				score = Integer.parseInt(st.nextToken());
				if(num != 0) { sum += score; }
				scoreArr[num++] = score;
			}
			cases[i] = temp;
			avg[i] = sum / (scoreCount-1);
		}
        
		double count;
		for (int i = 0; i < c; i++) {
			count = 0;
			for (int k = 1; k < cases[i].length; k++) {
				if(cases[i][k] > avg[i]) count++; 
			}
			System.out.printf("%.3f%%\n", (count / cases[i][0] * 100));
		}
	}
}

🔸 2번 풀이

이번엔 StringTokenizer을 거의 사용해본 경험이 없어
공부도 할 겸 split을 대신해 사용해봤다.

복잡한 문자열 파싱을 할 경우 split이 좋겠지만
split은 정규화 표현식을 사용하기에 간단한 문자열 파싱의 경우
StringTokenizer가 좀 더 빠른 속도를 낸다.

구시대의 유물이라고 부르는 사람도 있는 StringTokenizer인데
뭐든지 상황에 맞게 사용하여 좀 더 좋은 결과를 낼 수 있다면
무시하지 않고, 잘 배워 잘 활용해야 한다고 생각한다.

StringTokenizer 외에도 for문이 너무 많은 것 같아 조금 줄여보려고
점수의 합과 평균을 위쪽의 for문과 합쳐서 반복 횟수를 줄였다.

그 외에도 반복문 내에서 캐스트연산자로 인한 형변환이 있어
이 형변환을 없애기 위해 sum과 count를 double형으로 하여
캐스트연산자가 필요없도록 하였다.

결과는 메모리 13348KB / 처리시간 124ms로 단축되었지만
StringTokenizer를 거의 사용해보지 않은 탓인지
1번 코드보다 지저분해지고, 가독성이 떨어지는 느낌을 받았다.

코드의 가독성과 불필요한 코드를 줄이기 위해서
또 한번 도전했다.


🔹 3번

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.StringTokenizer;

public 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 = null;
		int c = Integer.parseInt(br.readLine());

		int[] scoreArr = null;
		double sum, avg, count;
		int score, scoreCount;

		for (int i = 0; i < c; i++) {
			st = new StringTokenizer(br.readLine());
			scoreCount = Integer.parseInt(st.nextToken());
            
			scoreArr = new int[scoreCount];
			sum = 0;
			for (int k = 0; k < scoreCount; k++) {
				score = Integer.parseInt(st.nextToken());
				sum += score;
				scoreArr[k] = score;				
			}
			avg = sum / scoreCount;
			count = 0;
			for (int k = 0; k < scoreCount; k++) {
				if(scoreArr[k] > avg) count++; 
			}
			bw.write(String.format("%.3f%%\n", count / scoreCount * 100));
		}
		bw.flush();
		bw.close();
		br.close();
	}
}

🔸 3번 풀이

이 3번 코드가 완성되기까지 코드를 수십 번이나 바꿔본 것 같다.

조금이라도 더 나은 가독성을 위해 처음 구조를 뜯어고치면서
불필요한 저장을 하지 않도록 cases 배열을 없앴다.

또한 위, 아래로 크게 두 개의 for문으로 되어 있었는데
이를 하나로 합쳤다.

for문을 두개로 나눴던 이유는
입출력을 같은 for문에서 할 경우 문제가 생겼었기 때문이다.

입력을 전부 받고 나서 출력을 해야 정상적인 입력과 출력이 되는데
기존 코드에선 for문을 하나로 합칠 경우 입력을 받고
다음 입력을 받기도 전에 출력이 되어버려
마지막 입력이 정상적으로 되지 않는 문제가 있었다.

그래서 println처럼 즉각 출력하지 않고, 어딘가에 저장했다가
한번에 출력할 수 있는 방법이 없을까? 하다
BufferedWriter라는 해결책을 떠올렸다.

BufferedWriter는 write만 할 경우 버퍼에 남아있다가
flush를 해 주어야 출력이 되기 때문에 이를 활용해서
입력, 출력 for문을 하나로 합치고
for문 내에서 write만 했다가 for문이 끝나고 flush를 하여
기존에 발생하던 문제를 해결했다!

BufferedReader는 println보다 속도도 빨라서
불필요한 낭비들을 줄인 것과 합쳐 기존보다 단축된
메모리 13284KB / 처리시간 116ms 로 통과하였으며
코드도 훨씬 깔끔해졌다 :)



😳❕ 소감 & 느낀점

아는 것이 힘이다!

라는 말은 어느 분야에서나 통하는 말이지만
개발자들에게 정말 중요한 말이라는 생각이 든다.

처음 코드나 두번째 코드를 보면 정말 불필요한 부분이 많이 보인다.

나 스스로도 코드를 작성하면서 "정말 이게 최선이야?"
라고 물을 정도로 스스로의 코드에 불만이 많았다.

하지만 StringTokenizer에 대해 좀 더 공부해보고,
BufferedWriter를 떠올려서 이들을 활용해보니
더 깔끔한 코드와 빨라진 처리속도를 얻을 수 있었다.

물론 마지막으로 작성한 3번 코드 또한 불필요한 부분이 있을거고,
좀 더 좋은 기능들을 가진 API, 클래스들을 활용해
더 빠르고 효율적인 코드를 짤 수도 있을 것이다.

그러니 앞으로도 더 좋은 지식들을 많이 흡수해서
이를 활용할 수 있는 개발자가 되도록 노력을 멈추지 말아야겠다. :)

0개의 댓글