프로그래머스 데브 코스 코딩 테스트

Tag ‘Coding-Test’

Coding-Test tag에는 내가 경험해본 코딩 테스트에 관한 후기를 남길 것이다.


프로그래머스 데브 코스 코딩 테스트 후기 (07/11 14:00~17:00)

한 달전에 생각없이 무료 강의 들을 수 있다길래 신청했었는데 서류가 통과될 줄 몰랐다.

내 생애 첫 코딩 테스트였다. 아직 Java도 공부를 다 못했는데 코테라니… 말도 안된다고 생각했고, 안칠려다가 살짝 맛이라도 보기 위해 테스트를 진행했다. 결론적으로 네트워크 지식을 물어보는 객관식 10문제와 프로그래밍 문제 3개가 나왔는데 처음하는 내가 풀 수 있을 정도니 난이도는 쉬움인 것 같다.


객관식 문제

수업을 들을 수 있는 자격을 부여하는 코딩 테스트여서 그런지 CS에 대한 아주아주 기초적인 지식만 있으면 풀 수 있었다. HTML이 HyperText Markup Language라는걸 물어보는 문제도 있었다.


프로그래밍 문제1

일단 언어는 무조건 Java였다.

문제 설명 요약…

보트는 1회용 보트이다. 보트에 짐을 실어서 왼쪽편에서 오른쪽으로 짐을 옮겨야 한다. 하지만 보트의 성능이 좋지 않아서 너무 짐을 많이 싣게 되면 보트가 망가져서 짐을 못 옮긴다.

1회용 보트가 적재할 수 있는 상자의 수는 알려져 있지 않고, 보트 별로 다를 수 있다.

첫 번째 보트에 1개 상자 실어서 보낸다. 이게 성공한다면 2번째 보트에는 이전에 실은 짐의 2배를 싣는다. 만약 실패하게 되면 다시 1개를 보낸다.

각 1회용 보트의 최대 적재량이 들어있는 배열 d 와 보내고자 하는 상자의 수 m이 주어질 때, 위에서 제시한 방법을 사용하여 상자를 보내는 경우 사용하게 되는 보트의 수를 return 하는 solution 함수를 완성해주세요.

만약 위와 같은 방식을 사용했는데, 보트의 수가 부족하여 m개를 보내는데 실패하는 경우는 -1을 return 해주세요.

입출력 예

d m result
[1,3,2,5,4] 6 5
[2,2,4,3] 6 3
[2,2,4,3] 8 -1


대략 이런 느낌의 문제였다. 총 3시간 동안 모든 문제를 풀면 됐다.

일단 이 문제를 보면서 ‘생각보다는 안어려운데…?’ 라고 생각했다. 그렇게 풀었더니 진짜 풀렸다… 이런 난이도는 쉬움 이라는 것을 몇시간 후에 알게됐다. Github link에 들어가면 내가 만든 실행시킬 수 있는 해답이 있다.

아래 코드는 내가 실제로 제출했던 코드이다. 이 코드는 테스트 코드 모두 통과했다.

class Solution {
  public int solution(int[] d, int m) {
    int answer = 0;
    int sendNum = 1;
    
    for(int i : d) {
      if(m <= 0)
        break;
      
      if(i >= sendNum) {
        m -= sendNum;
        sendNum *= 2;
        answer += 1;
      } else {
        sendNum = 1;
        answer += 1;
      }
    }
    
    if(m > 0)
      answer = -1;
    
    return answer;
  }
}
[1,3,2,5,4] 6
5

입력을 [1,3,2,5,4] 6로 주면 5가 출력되는 것을 확인할 수 있다. 이제 풀이를 해보자.


문제1 정리

간단하게 문제를 다시 정리해보자면,

<input example 1> [적재량1, 적재량2, 적재량3, 적재량4] m
보내고자 하는 상자 수 : m

[output]
3 : 사용하게 되는 보트의 수

입력을 [1,3,2,5,4] 6로 준다고 가정하고 문제에서 제시한 방법대로 처리해 나아가보자. 코딩테스트는 처음이라서 차근차근하게 될 수밖에 없었다.


문제1 흐름 파악하기

  1. 첫번째 보트의 적재량은 최대 1개. 우리는 1개를 실을 것이므로 보트는 안 망가지고 짐을 옮길 수 있다. - OK 남은 짐 6-1
  2. 두번째 보트의 적재량은 최대 3개. 우리는 1*2개를 실을 것이므로 보트는 안망가지고 짐을 옮길 수 있다. - OK 남은 짐 5-2
  3. 세번째 보트의 적재량은 최대 2개. 우리는 2*2개를 실을 것이므로 보트가 망가졌다. - FAIL 남은 짐 3
  4. 네번째 보트의 적재량은 최대 5개. 우리는 1개를 실을 것이므로 보트는 안 망가지고 짐을 옮길 수 있다. - OK 남은 짐 3-1
  5. 다섯번째 보트의 적재량은 최대 4개. 우리는 1*2개를 실을 것이므로 보트는 안망가지고 짐을 옮길 수 있다. -OK 남은 짐 2-2

총 5대의 보트가 있었고 5대를 모두 사용하여 짐을 모두 옮기는데 성공. 따라서 출력값은 5.


문제1 코드에 필요한 요소 생각

이제 위의 흐름을 코드로 적기만 하면 된다. 먼저 뭐가 필요한지 생각해봐야 한다.

  1. 변수
    • 실을 짐의 양을 표현할 변수 필요. sendNum 사용.
  2. 논리 흐름
    • 보트의 개수만큼 반복… for사용.
      • 현재 보트의 최대 적재량과 우리가 실을 짐의 양을 비교한 후 보트가 망가지는지 판단해야함. 비료를 위해 if사용.
        • 남은 짐(m)이 없다면,
          • 반복이 남았는지에 관계없이 반복문 종료. break;사용.
        • 망가지지 않았다면,
          • 남은 짐(m) - sendNum. 왜냐면 옮기는 것을 성공했기에 옮겨야 할 남은 짐이 줄어들기 때문이다.
          • sendNum 2배.
          • 보트 사용했기에 answer 변수에 1 추가.
        • 망가졌다면,
          • sendNum = 1로 초기화.
          • 보트 사용했기에 answer변수에 1 추가.
    • 보트의 개수만큼 반복이 끝났다면,
      • 남은 짐(m)이 0보다 크다면,
        • answer = -1. 왜냐면 준비한 보트로 짐을 모두 못 옮겼기 때문이다.
    • return answer; 코드 끝.

이런식으로 생각을 했었다. 정확히는 흐름만 파악하는데 실제로 메모장에 써가면서 시뮬레이션을 했었고, 코드로 옮길 때에는 논리에 맞게 한줄씩 코딩했던 것 같다. 체감 난이도는 였다. 문법도 검색해가면서 할 정도로 Java에 익숙하지도 않았던 내가 풀 수 있을 정도면…

목표는 1문제라도 건들여 보는 것인데 이렇게 하나를 풀었어서 되게 뿌듯했었다.


프로그래밍 문제2

문제 설명 요약…

입금 내역만 기록하는 통장이 있다. 통장에서는 입금과 출금이 모두 가능하지만 거래 내역에는 입금 내용만 표시된다.

  • 입금한 금액은 입금 순서대로 표시된다. 입금 내용마다 나누어서 표시.
  • 출금할 때는 가장 마지막에 입금한 내용부터 하나씩 삭제하며 출금액을 맞춘다.
    • 출금액을 맞춘 후 남은 액수를(1원 이상 남은 경우) 가장 마지막에 입금한 내역으로 추가한다.

입출력 예

Deposit Result
[500,1000,-300,200,-400,100,-100] [500,500]
[500,1000,2000,-1000,-1500,500] [500,500,500]

생각보다 2번 문제도 어려움이 없어 보였다. 배열의 값이 양수면 그냥 배열에 요소를 추가하고, 음수면 배열의 가장 마지막 값과 더해가면서 제거하면 되겠다고 생각했다.


문제2 경우의 수 + 필요한 것들 생각하기

  1. 입금할 때 경우
    1. answer 배열에 요소를 추가해주는 함수. -> addArr()
  2. 출금할 때에 answer 배열에 요소를 제거해주는 함수. -> removeArr()
  3. 출금할 때 경우,
    1. 현재 answer라는 배열의 맨 끝 요소와 출금할 금액 비교
      1. 배열의 맨 끝 요소의 값이 더 크다면, 출금금액 크기만큼 차감하고 answer배열 출력.
      2. 배열의 맨 끝 요소의 값이 더 작다면, 출금금액에서 배열의 요소값만큼 차감. 남은 출금할 금액은 을 뜻하는 변수에 저장. 배열의 맨 끝 요소는 removeArr()함수로 삭제.
        1. 배열의 맨 끝 요소의 값이 의 크기보다 크다면, 의 크기만큼 차감하고 answer배열 출력.
        2. 배열의 맨 끝 요소의 값이 의 크기보다 작다면, 에서 배열의 요소값만큼 차감. 남은 은 다시 을 뜻하는 변수에 저장. 배열의 맨 끝 요소는 removeArr()함수로 삭제.
          1. 배열의 맨 끝 요소의 값이…음?

보다시피 경우의 수를 나누다가 이상한 점을 발견했다. 반복을 하고 있었던 것. 데이터베이스를 배울 때도 그랬지만 개발자에게 있어서 반복이란 피해야할 존재와도 같다고 느꼈기 때문에 단순한 if, else만으로는 구현이 불가능할 것 같아 보였다.

그러다가 문득 예전에 학교 수업에서 배웠던 재귀 함수라는 것이 생각났고, 딱 이러한 경우에 쓰일 수 있을 것 같았다. 왜냐하면 자기자신을 반복하는 함수이기 때문에 코딩할 때 불필요한 코드의 반복을 피하면서 내가 원하는 기능을 수행할 수 있을 것 같았기 때문이다.

문제는 나는 한번도 재귀 함수를 구현해본적이 없다는 것이다. 애초에 학교 수업에서 들었을 그 당시 재귀 함수를 보고도 이해를 못하면서 넘어갔었다… 그래도 이번기회에 완벽히 이해하면 되지 않을까 라는 생각으로 도전해봤다.


문제2 재귀 함수 recusionF()

문제의 재귀 함수이다. 함수에 필요한 인자를 생각해보자.

  1. answer이라는 배열이 들어가야 한다.
  2. 배열의 맨 끝 값을 찾기 위한 index 값이 들어가야 한다.
  3. 이라는 값을 전달해야 하므로 해당 값이 들어가야 한다.

위 3가지가 들어가야 하므로 간단하게 함수의 prototype을 만들면, 아래와 같이 만들 수 있다.

public int[] recursionF(int[] answer, int money, int index)

함수 안에 기능을 할 코드는 경우의 수 나눈 것을 보고 만들면 된다.


문제2 간략한 구성

일단 재귀 ‘함수’니깐 해당 기능을 함수화 해줘야 한다. 따라서 총 3가지의 함수가 필요했다. addArr(), removeArr(), recursionF()순서대로 배열에 요소 추가 함수, 배열 요소 제거 함수, 재귀 함수 이다.

  1. deposit[]배열의 값을 for로 하나하나 받을 때,
    1. 배열의 값이 양수면 바로 answer배열에 요소 추가. -> addArr()사용.
    2. 배열의 값이 음수면 재귀 함수 사용.


문제2 테스트 입력으로 시뮬레이션

이번 문제는 배열의 길이도 문제1 보다 길고 생각해야 하는 경우의 수도 더 많았기 때문에 직접 하나하나 따져가면서 시뮬레이션을 했었다.

  1. [500]
    money=500 > 0 양수이기 때문에 그대로 answer배열에 추가. => answer = {500}

  2. [500, 1000]
    money=1000 > 0 양수이기 때문에 그대로 answer배열에 추가. => answer = {500, 1000}

  3. [500, 1000, -300]
    money=-300 < 0 음수이기 때문에 재귀 함수 실행.
    answer = {500, 1000} : 현재 answer 배열.
    money = -300 : 현재 들어온 돈(출금).
    index = answer.length = 2 : 배열의 맨 끝 값 찾기 위함.


    <재귀 함수> 실행.
    index - 1 = answer.length - 1= 2 - 1 = 1 : 배열의 맨 끝 값 찾기 위해 “배열크기 - 1” 진행.
    answer[1] = 1000 : 맨 끝 값 찾음.
    money = -300
    answer[1] + money > 0 양수이기 때문에 재귀 함수 종료. => answer = {500, 700}

  4. [500, 1000, -300, 200]
    money = 1000 > 0 => answer = {500, 700, 200}

  5. [500, 1000, -300, 200, -400]
    money = -400 < 0 : 음수이기 때문에 재귀 함수 실행.
    answer = {500, 700, 200}
    money = -400
    index = answer.length = 3


    <재귀 함수> 실행.
    index - 1 = 2
    answer[2] = 200
    money = -400
    answer[1] + money < 0 : 음수이기 때문에 또 다시 재귀 함수 실행.
    debt = answer[1] + money = -200 : 200만큼의 생김.
    answer = {500, 700} : 현재 answer 배열.


    < re : 재귀 함수> 실행.
    index - 1 = 1
    answer[1] = 700
    money = debt = -200
    answer[1] + money(debt) > 0 양수이기 때문에 재귀 함수 종료. => answer = {500, 500}

    결과 값 : answer = {500,500}


  6. 내가 설정한 테스트 배열 : [500, 1000, -300, 200, -1000]

    5번에서 내가 설정한 테스트 배열대로 들어오게 된다면?을 가정.

    money=-1000 < 0
    answer = {500, 700, 200}
    money = -1000
    index = answer.length = 3


    <재귀 함수> 실행.
    index - 1= 2
    answer[2]=200
    money=-1000
    answer[2] + money < 0
    debt = answer[2] + money = -800 이 남았기 때문에 한번 더 재귀 함수실행.
    answer = {500, 700}


    < re : 재귀 함수> 실행.
    index - 1 = 1
    answer[1] = 700
    money=debt=-800
    answer[1] + money < 0
    debt = answer[1] + money = -100 이 남았기 때문에 한번 더 재귀 함수실행.
    answer = {500}


    < re : re : 재귀 함수> 실행.
    index - 1 = 0
    answer[0] = 500
    money=debt=-100
    answer[0] + money > 0 => answer = {400}

    결과 값 : answer = {400}

위 와 같이 시뮬레이션을 했다. 이제 코딩만 할 차례.


문제2 제출 코드(테스트 모두 통과)

class Solution {
    public static int[] addArr(int n, int arr[], int element) {
        int i;
        int newarr[] = new int[n + 1];

        for (i = 0; i < n; i++)
            newarr[i] = arr[i];

        newarr[n] = element;

        return newarr;
    }

    public static int[] removeArr(int n, int arr[]) {
        int newarr[] = new int[n - 1];
        int i;

        for (i = 0; i < n - 1; i++) {
            newarr[i] = arr[i];
        }
        return newarr;
    }

    public int[] recursionF(int[] answer, int money, int index) {
        int debt;
        index -= 1;

        if(answer[index] + money > 0) {
            answer[index] += money;
        } else if(answer[index] + money == 0) {
            answer = removeArr(answer.length, answer);
        } else if(answer[index] + money < 0) {
            debt = answer[index] + money;
            answer = removeArr(answer.length, answer);
            return recursionF(answer, debt, answer.length);                    
        }
        return answer;
    }

    public int[] solution(int[] deposit) {
        int[] answer = {};

        for(int money : deposit) {
            if(money >= 0)
                answer = addArr(answer.length, answer, money); // add element in answer array
            else
                answer = recursionF(answer, money, answer.length);
        }

        return answer;
    }
}


후기

인생 첫 코딩테스트 여서 매우 의미가 있었고 뭐라도 끄적일 수 있어서 뿌듯함이 컸다. 문제3은 풀다가 시간이 다 돼서 사라졌기 때문에… 나중에 시간이 될 때 풀어보도록 할 것이다.

너무나 부족함이 많음을 이미 알고 있었지만 다시 깨닳았고 Java 공부 본격적으로 시작해야겠다…