취업이나 이직을 준비하는 개발자에게 코딩 테스트는 언제나 마음 한 구석에 남아있는 고민거리라 할 수 있다. 많은 이들이 코딩 테스트 무용론을 이야기하지만 안타깝게도 많은 회사가 아직까지도 코딩 테스트를 통해 개발자를 선별한다. 향후 코딩 테스트의 전망이 어떻게 될지 모르겠지만, 이번 글에서는 코딩 테스트와 관련된 강의를 하고 책을 집필해본 입장에서 코딩 테스트에 대한 다양한 이야기를 해보고자 한다.

코딩 테스트는 정말 필요한가?

코딩 테스트가 정말로 개발자에게 중요한 역량인지 여부는 논란이 많다. 현업에서 일하는 많은 전문가가 필요없다고 말하기도 하고 실제로 코딩 테스트를 통해 익힌 지식을 실무에서 써먹는 경우도 드물다. 필자 또한 코딩 테스트를 위해 열심히 배운 알고리즘을 실무에서 써먹은 적은 드물다. 최적화 또한 인프라 요소를 튜닝하는 것이 더 나을지도 모른다.

그래서 코딩 테스트가 좋은 개발자를 채용하는 것에 도움이 되는지에 대한 의문을 가질 수 있고 타당한 의문이라고 생각한다. 그렇지만 필자 개인은 코딩 테스트가 채용은 몰라도 개발자 개인에게 도움이 된다는 입장이다. 이왕이면 도움이 될 수 있는 점을 생각하며 준비하면 조금 기분이 나아지지 않을까?

아니면 말구...

네이티브 코더

필자는 개발자의 언어 능력이 중요하다고 생각한다. 여기서 말하는 언어 능력은 상호간 소통 능력을 말하는 것은 아닌 코드를 작성하는 능력을 뜻한다. 우리는 생각을 언어로 표현한다. 그리고 표현할 때 딱히 변환 과정을 거치지 않는다. 매우 자연스럽고 빠르게 생각을 언어로 표현한다. 어떤 경우엔 생각보다 말이 먼저 나오기도 한다. 그리고 그것이 가능한 사람을 보통 네이티브 스피커라고 부른다. 마찬가지로 개발자도 프로그래밍 언어를 통해 생각을 표현한다. 언어에 숙련된 개발자는 생각한 것을 거의 바로 손가락 끝으로 표현할 수 있다.

앞서 말한 것 처럼 코딩에 익숙하지 않다면 생각한 것을 표현하는 것이 어렵다. 문제를 이해했지만 코드로 나타내는 것이 어렵다고 느낀 적이 있는가? 이는 생각을 말로 표현하는 것이 어려운 것과 같다. 우스갯소리로 필자는 생각한 것을 막힘 없이 코드로 나타낼 수 있는 개발자를 네이티브 코더라고 부른다. 문제 해결에 있어서 겨우 코드 몇 줄 빠르게 작성하는 것이 큰 도움이 안될 것이라 생각할 수 있지만 생각한 것을 바로 표현하지 못하는 것은 몰입을 방해한다. 생산성 측면에서 몰입이란 정말 중요한 것으로 몰입이 깨지면 다시 몰입 상태로 돌아가기까지 시간이 걸리며 중요한 꺠달음을 놓칠 수 있다. 그러니 이왕이면 코드를 잘 작성할 수 있는 것이 좋다.

그럼 어떻게 네이티브 코더가 될 수 있을까? 많은 전문가가 특정 언어를 가장 빨리 익히는 방법은 최대한 많이 접하고 사용하는 것이라 말한다. 프로그래밍 언어 또한 마찬가지로 많이 써봐야 잘 표현할 수 있게 된다. 그런 측면에서 코딩 테스트를 위한 연습은 프로그래밍 언어와 그를 넘어 컴퓨터 세상에 내 생각을 표현할 수 있는 능력을 키우는데 도움이 된다. 한 가지 더 말하자면 로직을 이루는 것이 무엇인지 잘 이해하는 것도 중요하다. 프로그램을 이루는 로직은 크게보면 결국 순차, 조건, 반복, 참조의 연속이다. 아무리 추상화를 해도 로직 밑바닥은 이 네 가지로 이루어져 있고 이를 통해 우리가 생각한 로직을 표현해야 한다. 따라서 이 네 가지를 통해 생각한 것을 표현하는 능력이 중요하며 이를 잘 다루는 것이 기본기라 할 수 있다.

어쩌면 AI가 코딩해주는 세상에 이런 것이 필요한지에 대한 의문을 가질 수도 있다. 그러나 아직까진 AI가 만들어준 코드를 완벽하게 신뢰할 수 없기에 검증하는 능력이 필요하다. 또한 AI가 만들어주는 코드가 항상 최적화된 코드는 아니기에 최적화하는 능력도 필요하다. 결국 이를 위해 코드를 읽는 독해 능력이 필요하다.

컴퓨팅 사고

컴퓨팅 사고란 컴퓨터가 효과적으로 수행할 수 있도록 문제를 정의하고 그에 대한 답을 기술하는 것이 포함된 사고 과정1을 말한다. 즉, 컴퓨터가 프로그램을 효과적으로 수행할 수 있게 만들기 위한 사고 방식이라 할 수 있다. 이를 위해서는 컴퓨터 세상 속의 한계와 규칙을 명확하게 이해하고 있어야 한다. 보통 코딩 테스트를 잘 보기 위해선 자료구조와 알고리즘에 대한 이해가 필요하다. 즉, 코딩 테스트 통과를 위해 어쩔 수 없이 공부한 것이 컴퓨팅 사고를 위한 기초가 될 수 있다.

자료구조와 알고리즘을 학습한다면 기본적으로 시간 복잡도와 공간 복잡도에 대한 개념을 익히게 된다. 이를 통해 컴퓨터 세상의 한계를 인식할 수 있게 되고 한계를 극복하기 위해 상황에 맞는 자료구조와 알고리즘을 선택할 수 있게 된다. 즉, 로직 최적화를 할 수 있는 개발자가 되는 것이다.

개발 도메인에 따라 다르지만 시장 대다수를 차지하는 프로그램은 대부분 데이터를 잘 적재하고 사용자에게 잘 보여주기 위한 프로그램이다. 그런 프로그램은 데이터를 변형하고 연산하고 다른 데이터와 합치는 등 다양한 작업을 수행한다. 이런 작업을 효율적으로 수행하기 위해선 자료구조에 대한 이해는 필수라고 할 수 있다.

언젠가 찾아올 기회

필자는 자주 사용하지 않는 기술이더라도 익히고 있다면 언젠가 사용할 기회가 찾아온다고 믿는다. 이는 코딩 테스트를 위해 공부한 기술도 마찬가지다. 앞서 실무에서 알고리즘을 써먹은 적이 드물다고 했지만, 그 뜻은 없지는 않다라는 의미다. 그리고 업계에 따라 빈도가 훨씬 많은 경우도 분명 존재한다. 많은 사람이 남들이 해결하지 못한 문제를 멋지게 해내는 것을 상상해본 적이 있을 것이다. 그리고 보통 그런 경우는 남들이 가지않은 길을 걸은 사람이 쟁취하는 경우가 많다. 따라서 어느날 올 기회를 위해 코딩 테스트를 통해 능력을 키워두는 것은 좋은 선택이 될 수 있다. 다음과 같은 사례도 참고해보자.

멘탈 수련

어려운 문제를 마주해본 적 있는 사람과 아닌 사람은 큰 차이가 있다. 물론 개인차가 있을 수 있지만 어려운 문제를 마주하고 극복해본 적 있는 사람은 스트레스에 강하다. 꼭 코딩 테스트를 통해 기를 수 있는 능력은 아니지만 코딩 테스트 준비를 통해 얻은 경험은 추후 진짜 문제를 해결할 때 즐길 수 있는 사람이 될 수 있게 만들어 준다고 생각한다. 즉, 엉덩이를 의자에 더 오래 붙일 수 있는 능력을 키울 수 있다.

준비와 불안감

코딩 테스트를 준비하는 것은 쉽지 않다. 그러다보니 쉽게 불안해지고 많은 스트레스를 받는다. 여기서는 강의를 진행하며 들었던 다양한 질문에 대해 이야기해보고자 한다.

언어 선택

코딩 테스트를 본격적으로 준비하기 전 많은 사람들이 언어 선택에 대해 고민한다. 특히 아직 본격적으로 일해본 적이 없는 학생인 경우 더욱 그렇다. 그러면 어떤 언어를 선택하면 좋을까? 보통 코딩 테스트에서는 C++, Java, Python이 많이 사용된다. 그 중에서도 Python을 추천하는 경우가 많은데 Python은 내장된 여러 기능이 많아 더욱 쉽게 코드를 작성할 수 있기 때문이다. 예를 들어, JavaScript에선 큐를 사용하기 위해 직접 구현해야 하지만 Python에선 collections 모듈을 통해 deque를 사용할 수 있다. 또한 Python은 동적 타입 언어로 타입을 명시하지 않아도 되기에 더욱 편하고 빠르게 코드를 작성할 수 있다.

그렇지만 사실 언어는 크게 중요하지 않다. 자료구조의 원리를 정확히 이해하고 있다면 직접 구현하는 것은 크게 어려운 일이 아니다. 물론 실전에서 시간을 줄여줄 수는 있지만 못푸는 문제를 풀 수 있게 만들 정도의 시간 차이는 아니라고 생각한다. 그렇기에 언어 선택은 자신이 편한 언어를 선택하는 것이 좋고 딱히 그런 언어가 없다면 자신이 일하고 싶은 직군에서 많이 사용하는 언어를 선택하는 것도 좋다.

나는 왜 못풀지?

코딩 테스트를 하면서 문제를 못풀면 멘탈이 무너지는 경우가 많다. 어렵게 풀던 문제를 쉽게 푸는 사람을 보면 더욱 그렇다. 그렇지만 내게 재능이 없는 건 아닌지 고민할 필요는 없다. 대게 스스로 창의력이 부족한 것은 아닌지 고민하는 경우가 많은데 창의력이란 새로운 생각이나 개념을 발견하거나 기존에 있던 생각이나 개념들을 조합하여 새로이 생각해내는 특성이다. 결국 컴퓨터 시스템이라는 제한된 환경에서 내가 아는 지식과 경험을 이용하는 것이라 할 수 있다.

코딩 테스트로 출제되는 문제는 흔히 말하는 재능의 영역이라 보기 어렵다. 만약 내가 문제를 못푼다면 그건 재능이 없어서가 아닌 아직 충분한 시간을 쏟지 않았기 때문이다. 자신감을 잃기보다는 하는 것이 중요하다.

문제 접근 전략

사실 코딩 테스트는 여타 다른 시험을 준비하는 것과 크게 다르지 않다. 코딩 능력이 중요하게 작용하지만 결국 시간 제한이 있고 문제 유형이 정해져 있기에 이에 대한 대비가 중요하다.

코딩 테스트는 문제에 대한 접근을 어떻게 하는가가 중요하다. 이를 위해 전략을 세워야 하는데 필자는 가능성을 좁혀나가는 방법을 선호한다. 풀어서 설명하자면 문제를 읽고 가능한 유형을 좁히며 가장 가능성이 높은 유형으로 접근하는 것이다. 문제를 보면 다음과 같이 유형을 좁힐 수 있는 힌트가 주어진다.

  • 입출력 제한
  • 문제 키워드
  • 이미지
  • 문제 순서

입출력 제한을 먼저 확인하자

문제에서 주어진 입출력 제한에는 많은 힌트가 담겨있다. 이를 알고 문제를 읽는 것과 모르고 읽는 것은 큰 차이가 있기 때문에 가장 먼저 확인하는 것이 좋다. 예를 들어, 문제에서 주어진 입력의 크기가 100,000이라면 이를 통해 문제가 요구하는 시간 복잡도를 예측할 수 있다. 이를 통해 사용할 알고리즘의 범위를 좁힐 수 있다. 다음과 같이 분류해볼 수 있다.

  • 입력이 1,000 이하인 경우
    • 단순 구현 문제
    • 완전 탐색
    • 백트래킹
    • 시뮬레이션
  • 입력이 10,000 이하인 경우
    • 최대 O(N^2) 이내로 풀어야 하는 문제
    • 이중 반복문
  • 입력이 1,000,000 이하인 경우
    • 최대 O(NlogN) 이내로 풀어야 하는 문제
    • 정렬
    • 이진 탐색
    • 우선순위 큐(힙)
    • 다익스트라
    • 위상 정렬
  • 입력이 100,000,000 이하인 경우
    • 최대 O(N) 이내로 풀어야 하는 문제
    • 투 포인터
    • 슬라이딩 윈도우
    • 동적 계획법
    • 그리디
  • 입력이 100,000,000 보다 큰 경우
    • 최대 O(logN) 이내로 풀어야 하는 문제
    • 이진 탐색
    • 입력 값이 함정인 경우

위에서 정리한 것은 자주 나오는 유형을 정리한 것으로 다른 알고리즘이 있을 수 있다. 아직 감이 안올 수 있으니 다음 입출력을 보고 문제 유형을 예측해보자.

위 문제는 프로그래머스에서 풀 수 있는 야근 지수라는 문제다. 문제를 보면 works라는 배열이 주어지고 길이는 최대 20,000이다. 위 분류법을 대입한다면 O(NlogN) 보다 느린 알고리즘은 사용할 수 없다는 것을 알 수 있다.2 결론만 말하자면 이 문제는 최대 힙을 사용하는 문제이다. 물론 입력 크기만 보고 한 번에 알 수는 없지만 가능성을 좁혔기 때문에 풀이에 대한 방향을 잡을 수 있다.

한 가지 문제를 더 살펴보자.

위 문제는 자물쇠와 열쇠라는 문제로 카카오 블라인드 채용 문제로 출제된 문제다. 입력 값을 보면 최대 20x20 크기의 2차원 배열이 주어진다. 모두 순회해도 400번이므로 많은 연산을 거쳐도 시간 제한에 걸리지 않을 가능성이 높다. 입력 크기를 염두에 두고 문제를 읽어보면 모든 경우를 다 확인해도 충분히 풀 수 있다는 추측이 가능하다. 따라서 이 문제의 유형은 시뮬레이션이자 완전 탐색이다.

이처럼 입출력 제한을 먼저 확인하면 가능성을 좁히는데 큰 도움이 된다.

키워드로 유형 좁히기

코딩 테스트 준비를 해본 사람은 알겠지만 쓸때 없이 문제가 굉장히 긴 경우가 많다. 1분 1초가 아까운 시점에 문제를 읽다보면 더 긴장하게 되고 그로 인해 풀 수 있는 문제도 못푸는 경우가 생긴다. 따라서 문제를 처음부터 꼼꼼히 읽기보단 키워드를 먼저 찾는다는 마음가짐이 더 중요하다. 특정 키워드는 문제 풀이에 핵심적인 힌트를 주는 경우가 많다. 다음 문제를 살펴보자.

위 문제를 보면 위의 모든 조건을 만족하는 d의 최솟값이라는 문장이 있다. 보통 X라는 조건을 만족하는 가장 최대/최소값을 찾아라와 같은 문장은 높은 확률로 이진 탐색(파라메트릭 서치)를 사용하는 문제일 가능성이 높다. 한 가지 더 살펴보자.

위 문제에선 가장 맵지 않은 음식, 두 번째로 맵지 않은 음식이라는 문장이 있다. 이어서 스코빌 지수가 K 이상이 될 떄 까지 반복이라는 문장을 보면 계속해서 가장 맵지 않은 음식을 찾아야 한다는 것을 알 수 있다. 이런 키워드를 보면 우선순위 큐(힙)을 사용하는 문제일 가능성이 높다는 것을 알 수 있다.

주의할 점으로 특정 키워드를 파악하는 것은 문제 풀이에 대한 경험을 어느정도 요구한다. 처음 보는 유형에 대해 키워드를 파악하는 것은 불가능하기 때문에 많은 문제를 풀어보고 특정 키워드를 파악하는 능력을 키우는 것이 중요하다.

이미지로 유형 좁히기

문제에서 주어진 이미지 또한 문제 유형을 파악하는데 큰 도움이 된다. 이미지가 없는 문제도 있지만 이미지가 주어진다면 어떤 의미가 있는지 꼭 확인하자. 다음 문제를 살펴보자.

위 문제의 이미지를 살펴보면 같은 색상의 칸이 연결되어 있다. 이런 이미지가 주어진다면 문제 내용에 따라 갈리지만 높은 확률로 그래프 탐색 문제거나 영역 구분을 위해 BFS, DFS를 사용하는 문제일 가능성이 높다. 참고로 위 문제는 BFS나 DFS를 사용하는 문제다. 다른 문제도 살펴보자.

위 문제의 이미지를 살펴보면 정점과 간선, 간선의 가중치가 주어진다. 이를 통해 무방향 그래프라는 것을 알 수 있고 높은 확률로 그래프 문제임을 알 수 있다. 그래프 문제를 알고리즘으로 좁히면 셋 중 하나일 가능성이 높다.

  • 최단 경로 찾기
  • 최소 신장 트리
  • 위상 정렬

하지만 무방향 그래프이므로 위상 정렬은 아닐 것이다. 따라서 최단 경로 찾기 또는 최소 신장 트리 문제일 가능성이 높다. 참고로 위 문제는 최단 경로 찾기 유형이다.

문제 순서로 유형 파악하기

어떻게보면 조금 치사한 방법일 수 있다. 보통 코딩 테스트 문제는 쉬운 문제부터 어려운 문제 순으로 나열되어 있다. 따라서 어려운 유형인 동적 계획법이나 백트래킹 등의 유형은 앞쪽에 나오지 않는다.

보통 앞쪽 문제는 문자열 처리나 시뮬레이션과 같은 구현 문제일 가능성이 높다. 중간 문제는 유형 문제로 힙, 그래프, 트리 등을 이용한 알고리즘 문제가 자주 나오며 끝쪽은 동적 계획법이나 백트래킹, 그리디와 같은 방법론 문제가 나오는 경우가 많다. 이를 고려하여 문제 유형을 생각해보는 것도 좋은 방법이다.

물론 함정이 있을 수 있기에 무조건적으로 이 방법을 사용하는 것은 좋지 않다.

문제 유형을 파악하기 힘든 경우

간혹 모든 힌트를 봐도 문제 유형을 파악하기 힘든 경우가 있다. 이런 경우는 내가 아직 모르는 유형이거나 방법론 문제일 가능성이 높다. 여기서 말하는 방법론 문제란 동적 계획법이나 백트래킹, 그리디와 같이 특정한 방법론을 사용하는 문제를 말한다. 이런 경우는 문제를 잘 읽어보며 규칙을 찾아내는 것이 중요하다. 이런 문제를 잘 풀기 위해선 최대한 많이 풀어보며 규칙을 찾아내는 능력을 키우는 것이 중요하다. 그리고 이러한 방법론 문제 또한 세세한 유형으로 나누는 것이 가능하므로 자신만의 분류법을 만들어두는 것도 좋다.

학습법

이런 말을하면 싫어하는 사람들이 많지만 코딩 테스트 준비에서 가장 좋은 방법은 최대한 많이 풀어보는 것이다. 많이 풀어볼 수록 나만의 비법이 생기며 이로 인해 이 입력과 조건이면 이 방법 말고는 없다와 같은 필연성을 느끼게 된다. 그렇지만 학습에 대한 전략이 없는 것은 아니다. 다음과 같은 학습법을 추천한다.

단계별 학습

만약 코딩 테스트가 처음이거나 코딩이 익숙하지 않다면 아직 언어의 문법과 논리적 구현을 연습해야하는 단계라 할 수 있다. 이때는 쉬운 문제를 풀며 코드 작성에 익숙해지는 것이 좋다. 프로그래머스 기준으로는 레벨 1 ~ 2 문제, LeetCode 기준으로는 Easy 문제를 풀어보는 것이 좋다. 이를 통해 코드 작성에 익숙해지고 시간 복잡도 계산 연습도 할 수 있다.

만약 이 과정을 거쳐 간단한 문제는 충분히 풀 수 있지만 다음 단계로 넘어가기 어렵다면 자료구조와 알고리즘에 대한 지식을 쌓아야 한다. 단순한 구현 문제를 넘어가면 알아야만 풀 수 있는 문제들이 등장한다. 다익스트라 알고리즘을 모르는 상태에선 천재가 아닌 이상 절대로 해당 유형 문제를 풀 수 없다. 이 단계에선 책이나 강의를 통해 기초 지식을 익히고 문제와 해답을 보며 문제 유형을 파악하는 능력을 키우는 것이 좋다.

어려운 문제를 못풀겠다면 거의 다 온것이다. 이 단계에선 문제 자체가 복잡하여 직관만으로 풀기 어려운 경우가 많다. 이때는 문제에서 규칙을 찾거나 문제를 잘게 분해하여 작은 문제를 해결해나가는 훈련이 필요하다. 이를 위해 동적 계획법, 백트래킹 등의 문제를 많이 풀어보는 것이 좋다.

구현 문제와 유형 문제

앞서 계속 언급했지만 크게 코딩 테스트 문제는 구현 문제와 유형 문제로 나눌 수 있다. 구현 문제는 주어진 문제를 그대로 구현하는 문제로 생각한 것을 코드로 잘 옮기는 능력이 중요하다. 보통 구현 문제는 특정 알고리즘을 몰라도 풀 수 있게끔 설계된 경우가 많다. 그래서 네이티브 코더일 수록 잘 풀 수 있는 문제이다. 구현 문제를 잘 풀기 위해서는 최대한 많이 코딩을 해보는 것이 중요하다. 데이터를 다루는 것과 논리를 다루는 것에 익숙해야 하며 결정론적인 방법으로 문제를 해결하는 것이 중요하다. 또한, 내가 작성한 코드의 시간복잡도를 계산할 수 있어야 한다.

구현 문제와 달리 유형 문제는 주어진 문제를 특정한 방법을 이용하여 풀어야 하는 문제로 문제 유형을 파악하고 방법을 정확히 아는 것이 중요하다. 따라서 유형 문제를 풀기 위해서는 반드시 자료구조와 알고리즘에 대한 지식이 필요하다. 모르면 절대로 풀 수 없다.

안풀리는 문제

간혹 오랫동안 풀지 못하는 문제가 있다. 이럴때 끝까지 고민해야 한다는 주장과 답을 봐야한다는 주장 둘로 나뉘는데 필자는 3시간이 지나면 답을 보자는 의견이다. 우리의 시간은 무한하지 않다. 다른 공부를 할 시간도 부족하므로 3시간이 넘어가면 효율적인 학습이 아니라고 생각한다.

물론 답만 보는 것은 좋지 않다. 중요한 것은 답을 보고 이해하는 것, 유형에 대한 힌트가 무엇인지 파악하는 것이다. 그리고 비슷한 문제를 풀어보며 이해를 깊게 하는 것이 중요하다. 이런 과정을 통해 다음에 비슷한 문제가 나왔을 때 빠르게 풀 수 있게 된다.

마치며

이번 글에서는 코딩 테스트에 대해 다양한 이야기를 해보았다. 나눠서 작성해야 할 주제를 한 글에 몰아 넣다보니 애매하지만 코딩 테스트를 준비하는 사람들에게 도움이 되었으면 좋겠다. 중요한 것은 꾸준함이다. 대회에 나갈 것이 아닌 코딩 테스트를 통과하는 것이 목표라면 꾸준함으로 충분히 극복할 수 있다. 매일 한 문제를 풀어보며 경험치를 쌓아보자.

마지막으로 코딩 테스트를 준비할 때 가장 중요한 것은 나 자신이 문제를 잘 마주할 수 있는 사람이 되는 것을 목표로 해야 한다는 점이다. 공부한 것은 시간이 지나며 조금씩 잊혀질 수 있지만 경험은 오래도록 남는다. 코딩 테스트를 단지 합격을 위한 수단이 아닌 나 자신을 성장시키는 수단으로 삼아보자.

Footnotes

  1. Computational Thinking - Wikipedia

  2. 물론 최적화를 하여 해결하는 유형일 수도 있기에 항상 예외가 있을 수 있음을 염두하는 것이 좋다