이번엔 백준 10818번 문제를 풀려한다. 이 문제는 굳이 [배열]에 분류되었어야 하나 싶은 문제다.
따라서 배열을 쓰지 않고 푸는 방법, 배열을 쓰고 푸는 방법 총 두 가지로 풀 예정이다.
우선 지금은 배열을 쓰지 않고 풀 것이다. 테스트 케이스가 몇 개인지 주어졌기 때문에, 해당 횟수만큼 숫자를 입력받고, 최대 최소를 결정하면 된다.
최솟값은 1000001, 최댓값은 -1000001로 초기화시킨다. 그 이유는 이 값들은 입력될 범위보다 크거나 작기 때문에, 어떤 수도 1000001보다 작고, -1000001보다 크기 때문이다.
위처럼 최소, 최댓값을 초기화 시키고, 앞으로 들어오는 수에 대하여 최소보다 작다면 최솟값을 갱신시킨다. 마찬가지로 최대보다 크다면 최댓값을 갱신시킨다.
이를 통해 배열에 굳이 숫자를 집어넣지 않고도 계산이 가능하다.
최종 코드
import java.util.Scanner;
public class Number10818 {
public static void main(String args[]){
Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int min = 1000001, max = -1000001;
for(int i = 0; i < N; i++){
int cur = scan.nextInt();
min = (Math.min(cur, min));
max = (Math.max(cur, max));
}
System.out.println(min + " " + max);
}
}
오늘은 백준 1110번 문제를 풀이하려한다. 사실 코딩 자체는 쉬운데 말이 어려워서..(일부러 어렵게 낸건가..)
그래서 주어진 문제를 내가 조금 정리해봤다.
1. 정수 base가 주어진다. (0 <= base <= 99) 2. base가 10보다 작다면, 앞에 0을 붙여 두 자리 수로 만들고,각 자리의 숫자를 더한다. 3. base의 일의 자리 숫자와 앞에서 구한 합의 가장 오른쪽 수를 이어 붙여 새로운 수 calc를 만든다. 4. base와 calc가 같다면 프로세스를 종료시킨다.
이 때, 2번의 앞 내용을 빨간줄로 그어 놓은 이유는 다음과 같다.
한 자리 숫자 1은 01로 쓸 수 있다. 한 자리 숫자 2는 02로 쓸 수 있다. ... 한 자리 숫자 n은 0n으로 쓸 수 있다.
이제 base에 대하여 일의 자리 수와 십의 자리 수를 구할 것인데, 이는 나머지 연산으로 쉽게 구할 수 있다. 본문에 가장 오른쪽 자리 수(일의 자리 수)가 먼저 나왔기 때문에, A를 일의 자리 수, B를 십의 자리 수를 표현하는 데 사용할 것이다.
따라서 A와 B는 다음과 같다.
A = base % 10; B = base / 10;
이제 이 A와 B를 가지고 새로운 calc를 구할 것인데, 3번을 보면 "일의 자리 숫자와 앞에서 구한 합"을 구해야 한다.
일의 자리 숫자는 A이고, "앞에서 구한 합"은 2번을 보면, "각 자리의 숫자를 더한다"기 때문에, A+B가 된다. 여기에 대하여, 가장 오른쪽 수 즉 일의 자리 수이고, 따라서 calc는 다음과 같다.
calc = A + ( (A+B)%10 );
이 calc를 base와 비교하여 종료하면 된다.
최종 코드
import java.util.Scanner;
public class Number1110 {
public static void main(String args[]){
Scanner scan = new Scanner(System.in);
int base = scan.nextInt();
int calc = base;
int A = 0, B = 0;
int repeat = 0;
do{
A = calc % 10;
B = calc / 10;
calc = (A*10)+((A+B)%10);
repeat++;
}while(base != calc);
System.out.println(repeat);
}
}
백준 15552번 빠른 A+B 풀이를 하려 한다. 이 문제에 빠른이라는 특징이 붙은 이유는 시간 제한이 있기 때문이다.
Java를 사용하고 있다면 Scanner 대신 BufferedReader, System.out.println 대신 BufferedWriter를 사용할 수 있다.
무엇이 다르길래 Scanner와 System.out.println 대신 사용하라는 것일까?
Scanner 대신 BufferedReader
우선 Scanner와 BufferedReader를 비교해보자.
Scanner는 어떤 입력에 대하여 parse하는 것을 알 수 있다. 무슨말이냐면, 우리가 scanner를 통해 입력을 받을 때, 다음과 같은 함수들을 사용할 수 있다.
int i = scan.nextInt();
byte b = scan.nextByte();
String s = scan.next();
float f = scan.nextFloat();
double d = scan.nextDouble();
반면에 bufferedReader는 다음과 같은 함수만 사용할 수 있다.
char c = bufferedReader.read();
String s = bufferedReader.readLine();
여기서 우리가 알 수 있는 것은 String = Array of Character이고, 따라서 입력 문자를 그대로 읽기만 할 수 있는 것이다.
어느 정도 감이 잡힐 텐데, 속도차이가 발생하는 이유는 바로 문자를 특정하게 읽을 것이냐 vs 있는 그대로 읽을 것이냐의 차이가 있기 때문이다.
여기서 한가지 팁을 얻을 수 있는데, 우리가 파일을 읽을 때는 parsing이 필요 없고, 따라서 bufferedReader를 사용하는 것이 좋다.
Syso 대신 BufferedWriter
그러면 System.out.println과 BufferedWriter는 어떻게 다를까?
Scanner와 BufferedReader의 차이와 비슷한데, 우선 System.out.println은 PrintWriter고, BufferedWriter는 BufferedWriter다.
여기서 Scanner와 BufferedReader의 차이와 같은 이유로 속도의 차이가 발생한다.
PrintWriter는 형식화된 표현을 출력하고, BufferedWriter는 character를 그대로 출력하는 데, 이 때 BufferedWriter의 이름에서 볼 수 있듯이 후자가 buffer관리에 조금 더 유리하다.
정리하자면, Scanner와 System.out.println은 어떤 정형화된 입출력을 다루고, BufferedReader / BufferedWriter는 있는 그대로의 데이터에 대하여 입출력을 수행하기 때문에 속도차이가 발생한다.
BufferedReader와 BufferedWriter의 사용법은 알고리즘에서 중요한 내용이 아니기 때문에, 단순하게 적고 가겠다.
Scanner scan = new Scanner(System.in)과 같이 Buffered Reader도 인풋을 받는 녀석이 필요하며, 이 때 Stream형식으로 설정다.
BufferedWriter의 경우, 마찬가지로 출력대상이 필요하며, Stream형식으로 설정한다.
해당 문제를 풀기 위한 나머지는 A+B와 같고, 따라서 설명은 생략하도록 하겠다.
해당 문제의 최종 코드는 다음과 같다.
import java.io.*;
public class Number15552 {
public static void main(String args[]) {
try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))){
try(BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out))){
int T = Integer.parseInt(bufferedReader.readLine());
for(int i = 0; i < T; i++){
String values = bufferedReader.readLine();
int A = Integer.parseInt(values.split(" ")[0]);
int B = Integer.parseInt(values.split(" ")[1]);
bufferedWriter.write(A+B + "\n");
}
}
} catch (IOException e){
e.printStackTrace();
}
}
}
맥북이 도착하고 난 뒤부터 코딩테스트를 시작하려 했지만, 맥북이 생각보다 늦게와서 (아니 한국에 들어왔는데 왜 배송이 안되냐고..ㅠㅠ) 데스크탑으로 공부를 시작했다!
앞에 문제들은 이미 풀어져 있길래,, 2588번부터 하나하나 단계별 풀어보기를 진행할 예정이다.
오늘은! 그 대망의 첫 번째 문제인 백준 2588 A*B를 풀 것이다.
오늘의 문제
2588번의 문제는 두 정수 A*B를 계산할 때, 계산 과정을 모두 출력하는 것이다.
이 문제를 딱 봤을 때, 정해야 할것 하나와 알아야 할 것이 두 가지가 있는 것을 알 수 있다.
정해야 할것 1. 두 정수 중 어떤 것을 베이스로 할것인가?
알야아 할것 1. 곱해지는 수는 몇자리 수 인가? (365=3자리 수) 2. 곱해지는 수의 각 자리의 값은 무엇인가? (일의 자리, 십의 자리, 백의 자리 ...)
정해야 할 것은 그냥 첫 번째 수를 베이스로 했고, 따라서 두번 째 수는 자연스럽게 곱해지는 수로 정했다.
해당 문제는 수학적 지식만 조금 있다면 바로 구할 수 있다. 나 같은 경우에는 위의 방법이 바로 떠올랐고, 이미 알고있는 방법이기 때문에 바로 문제를 풀 수 있었다. (세 번 실패한건 비밀... 첫 번째는 오타로인한 컴파일 에러.. 두 번째는 클래스 명이 Main이어야 한다는 것을 까먹고 있었고,, 따라서 틀렸다...)
1. 곱해지는 수는 몇자리 수 인가?
우선 곱해지는 수가 몇자리 수인지 알아야 한다. 우리에게 주어지는 곱의 값은 매번 달라질 것이고, 따라서 하드코딩을 통해 값을 매번 지정할 수 없기 때문이다.
예를 들어, 곱해지는 수가 한자리 수일때의 경우, 두자리 수일 때의 경우, 세자리 수일 때의 경우, ... 엄청 큰 자리수일 때의 경우 를 모두 코딩할수는 없잖아!
해당 질문은 log를 통해 쉽게 구할 수 있다. 먼저 자리수에 대해 우리가 표현할 때, 1의 자리는 다음과 같이 표현할 수 있다.
$$ n * 10^0 $$
마찬가지로 10의 자리는 다음과 같이 표현할 수 있다.
$$ n * 10^1 $$
이쯤 되면 내가 무엇을 하고자 하는지 이해 할 수 있다! 즉, 우리는 정수의 자리수를 표현하기 위해 다음과 같이 일반화 할 수 있다.
$$ n * 10^m ( m >= 0, integer ) $$
이 때,n은 우리가 표현하고자 하는 수이고,m은 해당 숫자의 자릿수이다.
우리는지수를 로그로 표현할 수 있는 것을 고등학교 수학에서 배웠다.
즉, 자리수를 로그를 통해 표현할 수 있는 것인데, 위의 일반화를 로그로 표현해 보면 다음과 같이 될 수 있다.
$$ m = log_10 n $$
이 때, m은 자리수가 되고, n은 표헌하고자 하는 수 이다! 그런데, log에서는 한가지 주의할 점이 있는데, log 0이 정의되지 않는 점이다.
$$ 10^m = 0 $$ log 0을 지수로 나타내었을 때 위와 같은 식이되고, 해당 식을 만족하는m의 값은 없기 때문이다.
따라서, 우리가 구하고자 하는 자릿수는 최종적으로 다음과 같은 식이 된다.
$$ m = {log_{10} n} + 1$$
2. 곱해지는 수의 각 자리의 값은 무엇인가? (일,십,백, ...의 자리)
이제 해당하는 m 자리의 값이 무엇인지 구하면 된다.
해당 질문은 놀랍게도 몫을 구하는 식으로 쉽게 풀 수 있다. 위에서 우리는 모든 수를 다음과 같이 표현할 수 있다는 걸 이미 인지했다.
$$ n = 10^m $$
우리는 궁극적으로 n이 무엇인지 구해야 한다. 수는 0~9까지 중 하나로 표현할 수 있고, 거기에 자릿수를 곱해주게 되면, n이 무엇인지 알 수 있다.
"그래서 도대체 0~9 중 무엇인지 어떻게 아는데?!"
우리가 어떤 수를 나눌 때, 해당 식은 다음과 같이 표현된다.
$$ N = Qx + R $$
아직도 감이 안오는가?!?!
우리가 구하고자 하는 수 N에 대하여 Q=10(몫의 밑)이고, x(몫의 배수)가 얼마든 간에 R의 범위는 0~9로 한정된다.
이 때 우리는 몫의 배수를 자리수로 해 줄 것이다. 무슨 말이냐면, 다음과 같이 나타낼 수 있다.
$$ 10^m % 10 $$
여기서 문제점은, 우리가 아무리 10 % 10, 100 % 10, 10....0, % 10을 해도 0이 나온다는 점이다. 그 이유는, 우리 본래 수를 아직 구하지 않았기 때문인데, 본래 우리가 찾고자 하는 수는 다음과 같이 구할 수 있다. 예를 들어, 385에 대하여 8을 구하고자 할 때는 다음과 같다.
$$ {385 / 10^1} % 10 = 8.5 $$
이 때, 우리가 사용하는 수는 정수이기 때문에, 8로 구해지는 것을 알 수 있다.
이제 위 두 수식을 근거로 코드를 작성해보자!
문제의 풀이 과정을 출력하기 위한 함수 하나, 그리고 메인함수 하나 하여 총 두 개의 함수를 만들 것이다.
메인 함수는 두 수를 입력받고, 풀이 과정을 출력하는 함수를 호출하며 최종적으로 두 식의 연산 결과를 출력할 것이다.
BufferedReader와 InputStreamReader를 이용하여 입력받을 수 있지만, 간단한 문제이고 메모리, 속도제한 등 아무 것도 없기 때문에 java.util.Scanner를 이용하여 두 수를 입력받을 것이다.
따라서 최종 코드는 다음과 같다.
public class Number2588{
public static void main(String args[]){
try(Scanner scan = new Scanner(System.in)){
int a = scan.nextInt();
int b = scan.nextInt();
printNth(a, b);
System.out.println(a*b);
}
}
public static void printNth(int a, int b){
int length = (int)(Math.log10(b)+1);
for(int i = 0; i < length; i++){
int tempB = b / (int)Math.pow(10, i) % 10; // tempB 없이 바로 출력해도 된다.
System.out.println(a * tempB);
}
}
}