본문 바로가기
Algorithm/BOJ

for문 편(1) - Node.js로 [백준/BOJ] 단계별로 풀어보기를 풀어보다

by Muko 2020. 4. 14.

BOJ 단계별로 풀어보기 'for문' - 1편

저번 포스팅에는 if을 다루었었고, 이번 포스팅에서는 반복문 중에서도 for문에 대해서 문제를 모아놓은 것을 풀어보도록 하겠습니다. 사실 백준 단계별로 풀어보기의 이번 단계 이름이 for문 이라고 작성되어있지만, 반복문을 동작시킬 수 있는 다른 문법을 사용해도 문제를 푸는게 가능합니다. 가장 보편적이고 처음에 직관적으로 이해하기 쉬운 것이 for문이라서 이렇게 이름을 작성한 것 같아요. 물론 for문과 while문의 사용방법이 조금 다르긴 하지만 동일하게 작동이 가능한데, 백준에서는 어떤 상황에서 각 반복문 문법을 사용하는 것이 좋은지 나름 생각해서 for문 편과 while문 편을 나누어서 문제집을 만들어 놓았더라구요. 핵심은 '어떤 문법이 항상 정답처럼 사용하지 않아도 된다'라는 점을 인지하시고 진행하시면 좋을 것 같습니다.

자, 그럼 for문 편에 있는 문제를 하나씩 풀어보도록 하겠습니다. 만약 for문에 대해 모르시는 분들이 있으시다면 Javascript 입문 - 반복문 _ for, while, forEach, map 편을 보고 와주세요!

1. BOJ 2739 - 구구단

이 문제는 어떤 숫자 N을 입력으로 받았을 때 그 숫자에 해당하는 구구단을 출력하는 문제입니다. 첫 번째 시간에 풀어보았던 사칙연산을 응용하면 쉽게 해결할 수 있는 문제입니다.

코드 접기/펼치기

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

const gugudan = (number) => {
  for(let i=1; i<=9; i++){
    console.log(number, '*', i, '=', number*i);
  }
}

rl.on("line", function(line) {
  const input = parseInt(line);
  gugudan(input);

  rl.close();
}).on("close", function() {
  process.exit();
});

보통 for문을 동작시킬 때 인덱스 i를 0부터 시작하는데요, 저는 이번 문제에서 구구단을 출력하거나 연산할 때 숫자가 1부터 시작해야하는게 명확하기 때문에 for문에서의 인덱스 i도 0이 아니라 1부터 시작하게끔 코드를 작성했습니다. 항상 0부터 시작해야 좋다는 강박관념에서 잠시 나오셔도 좋아요! 제가 전에 그랬거든요. 무조건 인덱스는 0부터 시작해야 깔끔한 코드이다라는 생각에 갇혀서 코딩했었는데, 굳이 그럴필요 없이 유연하게 작성하시면 됩니다.

2. BOJ 10950 - A+B - 3

이 문제는 두 개의 정수 A와 B를 입력받았을 때 A+B를 출력하는 코드를 작성하라는 문제입니다. 특이하게 이전 문제들과 달리 테스트 케이스라는 게 주어지는데요, 이 테스트 케이스의 개수만큼 예제 입력에서 문제가 들어오기 때문에 우리는 각 테스트 케이스마다 정답을 출력할 수 있도록 반복문을 작성해야 합니다. 만약 테스트 케이스가 5개가 주어진다면 우리는 반복문을 5번 돌면서 입력과 출력을 반복하면 되는거죠! 여기서 어떤 동작과 결과를 반환하는 함수를 미리 작성해놓는다면 더욱 코드가 깔끔하겠죠?

코드 접기/펼치기

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

const input = [];
rl.on('line', line => {
  input.push(line.split(' '));
}).on('close', () => {
  const T = parseInt(input[0]);
  for(let i=1; i<=T; i++){
    const A = parseInt(input[i][0]);
    const B = parseInt(input[i][1]);
    console.log(A+B);
  }

  process.exit();
})

여기서 input[i][0]이 부분이 이해가 되지 않으실 수 있는데요, 이 코드는 input에 어떤 값이 어떤 형식으로 저장되어있는지 이해를 해야합니다. 보시면 input에 값을 넣을 때 line.split(' ');이라는 코드를 사용했는데요, 입력이 테스트 케이스 수를 의미하는 숫자 한 개(첫 번째 줄)을 제외하고는 모두 'val1 val2의 형식으로 공백을 사이에 두고 입력을 들어오게끔 되어있기 때문입니다. 이렇게 코드를 작성하면 input에는['val1', 'val2']와 같은 형태로 저장이 되겠죠. 이렇게 저장되는게 테스트 케이스 수 만큼 되니까, 만약 T가 2라면 input에는['T', ['val1', 'val2'], ['val1', 'val2']]`와 같이 저장이 됩니다.

배열에서 요소에 접근할 때는 대괄호 ([ ])를 사용하고 그 안에 인덱스값을 넣어서 사용하는데요, 만약 이 부분에 대해서 잘 모르시는 분들은 자바스크립트 입문 - 배열 포스팅을 보고 오시면 도움이 되실겁니다.

그래서 정리하자면 input[i][0]이라는 코드의 의미는 input에 i번째 인덱스에 접근해서 다시 0번째 인덱스에 접근하겠다라는 의미입니다. 이 코드가 가능한 이유는 input[i]의 값이 ['5', '8']과 같이 또다시 배열로 들어있기 때문이죠. 그래서 이렇게 코드를 작성하면 우리가 원하는 두 개의 숫자값 A와 B를 가져올 수 있고, 결과값인 A+B를 출력할 수 있게 됩니다.

그런데 백준에서 Node.js로 문제를 풀기위해 입력을 받는 readline이라는 모듈에 .on('line')을 이용하면 자동으로 모든 입력 줄을 받아오게 되어있습니다. 파일의 끝을 만날 때 까지 줄 단위로 입력을 받거든요. 그래서 우리는 처음의 테스트케이스를 무시하고 출력하게끔 코드를 작성하면 됩니다.

코드 접기/펼치기

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.on('line', line => {
  const input = line.split(' ');

  if(input.length === 2) {
    const A = parseInt(input[0]);
    const B = parseInt(input[1]);
    console.log(A+B);
  }
}).on('close', () => {
  process.exit();
})

3. BOJ 8393 - 합

이번 문제는 전형적인 반복문 문제입니다. 말 그대로 반복문을 사용할 줄 아는지에 대해서 물어보는 문제라고 생각하시면 되는데요, 입력값 N에 대해서 1부터 N까지의 합을 출력하면 됩니다. 여기서 다른 프로그래밍 언어를 사용할 때는 조심해야할 부분이 바로 N의 최대 크기인데요, 보통 프로그래밍 언어에서 각 자료형은 사용가능한 최대값과 최소값이 있습니다. 그래서 만약 문제에서 제시하는 N의 최대값이 매우 크다면 자료형의 크기 제한에 벗어날 가능성이 있기 때문에 자신이 사용하는 자료형에 대해서 알고 있어야 합니다.

그런데 자바스크립트에서는 따로 명확하게 자료형을 작성하지 않죠. 하지만 자바스크립트에서도 정수에 대한 최대값이라는 제한이 있습니다. 그 제한은 다음과 같이 확인이 가능합니다.

console.log(Number.MAX_SAFE_INTEGER);

이에 대해서는 MDN Number.MAX_SAFE_INTEGER에 들어가서 살펴보시면 이해하시는데 도움이 될겁니다.

다행히도 이 문제에서는 N의 최대값이 1만입니다. 이는 10000 * 10001 / 2 = 약 5천만이기 때문에 무난하게 우리가 생각하는 풀이방법으로 해결할 수 있다는 것을 알 수 있습니다. 그럼 풀어보도록 하겠습니다.

코드 접기/펼치기

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

const finiteSum = (N) => {
  return N*(N+1)/2;
}

rl.on('line', line => {
  const input = parseInt(line);
  console.log(finiteSum(input));
}).on('close', () => {
  process.exit();
})

문제에서 원하는 답을 출력하기 위한 finiteSum 함수를 만들어서 풀었습니다. 여기서 사용한 공식은 가우스가 어렸을 때 수학시간에 1부터 100까지 더하라는 퀴즈를 빠르게 풀었다는 이야기 때문에 유명한 식인데요, A부터 B까지의 합을 구할 때 B부터 A까지의 합도 식을 적은 뒤, 그 둘을 합했을 때 결국에는 (A+B)의 값이 (B-A+1)개 만큼 존재하고, 식을 두 번 더한 것이므로 마지막에 2로 나눈 값이 정답이다라는 식입니다. 코드로 작성하면 아주 간단하죠?

4. BOJ 15552 - 빠른 A+B

이 문제는 A+B의 문제와 입력형식이 동일한데 T가 최대 백만이라는 조건이 생겼다는 것이 다릅니다. 자바스크립트에서 console.log를 통해 출력하는 것이 생각보다 많은 시간을 소비할 수 있는데요, 그 수가 백만이라면 1초 안에 출력하는 것은 불가능합니다. 다시말해 '시간초과'라는 결과를 받게된다는 의미입니다. 그래서 풀이방법은 완전히 동일하지만 (A+B의 결과값 출력), 이번에는 매 번 결과를 출력하지 않고 결과를 저장하는 곳에 차곡차곡 쌓아서 마지막에 출력하는 방식으로 풀어보겠습니다.

코드 접기/펼치기

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

let answer = '';
rl.on('line', line => {
  const input = line.split(' ');

  if(input.length === 2) {
    const A = parseInt(input[0]);
    const B = parseInt(input[1]);
    answer += A+B + '\n';
  }
}).on('close', () => {
  console.log(answer);
  process.exit();
})

여기서 제가 '\n'이라는 기호를 사용했는데요, 이는 개행 문자입니다. 이스케이프 시퀀스, 혹은 이스케이프 문자라고 네이버나 구글에 검색해보시면 찾을 수 있습니다. 그래서 A+B의 결과에 개행문자까지 하나의 String으로 계속해서 answer라는 변수에 저장되는 것이죠. 그리고 마지막에 이 answer 변수 하나만 출력하면 우리가 원하는 결과와 동일하게 출력하는 것이 가능합니다.

5. BOJ 2741 - N 찍기

이 문제도 매우 간단합니다. 주어진 입력 N에 대해서 1부터 N까지 출력하라는 문제입니다. 코드는 다음과 같습니다.

코드 접기/펼치기

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.on('line', line => {
  const N = parseInt(line);
  let result = '';
  for(let i=1; i<=N; i++){
    result += i + '\n';
  }
  console.log(result);
}).on('close', () => {
  process.exit();
})

이 문제도 N이 최대 10만이기 때문에 한 줄, 한 줄마다 출력하게 되면 시간이 오래걸릴 수 밖에 없습니다. 물론 백준 사이트에서 Node.js로 문제를 풀 경우에는 시간에 대한 핸디캡을 받아서 통과하기는 하는데, 위와 같이 해결하면 훨씬 더 빠르게 동작하는 것을 확인할 수 있습니다. 제출해본 결과 216ms와 4832ms라는 결과를 얻을 것을 보면 엄청나게 차이가 많이 나는 것을 확인할 수 있었습니다.

6. BOJ 2742 - 기찍 N

문제 이름에서 알 수 있듯이 이번에는 입력으로 들어온 N에 대해서 N부터 1까지 출력하는 문제입니다. 위의 문제와 달리 거꾸로 출력하는 문제이고, 풀이방법은 for문을 사용할 때 index를 어디서부터 시작해서 어떻게 변화시킬 것인지, 그리고 그것을 다룰줄 아는지에 대해서 물어보는 문제입니다.

코드 접기/펼치기

const readline = require('readline');
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.on('line', line => {
  const N = parseInt(line);
  let result = '';
  for(let i=N; i>0; i--){
    result += i + '\n';
  }
  console.log(result);
}).on('close', () => {
  process.exit();
})

여기까지 따라오시느라 수고하셨습니다.
다음 포스팅에서는 for문 문제 모음에 있는 7번 부터 11번까지 문제를 다루도록 하겠습니다 :)

댓글2

  • BlogIcon 오롱(ORONG) 2020.04.14 12:58 신고

    오.. 본문 내용을 이해할 순 없지만...😅 포스팅을 참 깔끔하게 잘 만드시네요!
    html이니 css니 한 번 공부해보자 결심만은 열댓번도 넘게 했던 것 같은데 개발은 제게 진입장벽이 너무 높더라구요... ㅎㅎ 잘 구경하고 갑니다 :)
    답글

    • BlogIcon Muko 2020.04.14 13:35 신고

      맞아요.. 혼자 시작하기엔 진입장벽이 좀 있죠 ㅠㅠ 그런데 막상 시작하면 생각보다 쉬워요!!!!