코딩 테스트 완벽 대비 가이드 | 알고리즘부터 실전 팁까지

코딩 테스트 완벽 대비 가이드 | 알고리즘부터 실전 팁까지

이 글의 핵심

코딩 테스트 완벽 대비 가이드입니다. 필수 알고리즘, 문제 풀이 전략, 실전 팁을 상세히 설명합니다.

들어가며: 코딩 테스트는 준비가 전부

”알고리즘을 몰라서 떨어졌어요”

코딩 테스트는 패턴 인식입니다. 자주 나오는 패턴을 익히고, 반복 연습하면 누구나 통과할 수 있습니다.

이 글에서 다루는 것:

  • 필수 알고리즘 및 자료구조
  • 문제 풀이 패턴
  • 시간 관리 전략
  • 언어 선택 가이드
  • 실전 팁

목차

  1. 필수 알고리즘
  2. 필수 자료구조
  3. 문제 풀이 패턴
  4. 시간 관리
  5. 언어 선택
  6. 실전 팁
  7. 정리

1. 필수 알고리즘

우선순위별 학습 순서

graph TB
    A[코딩 테스트 알고리즘] --> B[1단계: 필수]
    A --> C[2단계: 중요]
    A --> D[3단계: 고급]
    
    B --> B1[투 포인터]
    B --> B2[슬라이딩 윈도우]
    B --> B3[이진 탐색]
    B --> B4[BFS/DFS]
    
    C --> C1[동적 프로그래밍]
    C --> C2[그리디]
    C --> C3[백트래킹]
    
    D --> D1[최단 경로]
    D --> D2[위상 정렬]
    D --> D3[트라이]

1단계: 필수 알고리즘 (출제 빈도 높음)

투 포인터 (Two Pointers):

# 예제: 정렬된 배열에서 두 수의 합
def two_sum(arr, target):
    left, right = 0, len(arr) - 1
    
    while left < right:
        current_sum = arr[left] + arr[right]
        if current_sum == target:
            return [left, right]
        elif current_sum < target:
            left += 1
        else:
            right -= 1
    
    return None

# 시간복잡도: O(n)

슬라이딩 윈도우 (Sliding Window):

# 예제: 길이 k인 부분 배열의 최대 합
def max_sum_subarray(arr, k):
    window_sum = sum(arr[:k])
    max_sum = window_sum
    
    for i in range(k, len(arr)):
        window_sum += arr[i] - arr[i - k]
        max_sum = max(max_sum, window_sum)
    
    return max_sum

# 시간복잡도: O(n)

이진 탐색 (Binary Search):

# 예제: 정렬된 배열에서 값 찾기
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    
    return -1

# 시간복잡도: O(log n)

BFS/DFS (너비/깊이 우선 탐색):

from collections import deque

# BFS
def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        node = queue.popleft()
        print(node)
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

# DFS (재귀)
def dfs(graph, node, visited=None):
    if visited is None:
        visited = set()
    
    visited.add(node)
    print(node)
    
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

2단계: 중요 알고리즘

동적 프로그래밍 (DP):

# 예제: 피보나치 수열
def fibonacci(n):
    if n <= 1:
        return n
    
    dp = [0] * (n + 1)
    dp[1] = 1
    
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    
    return dp[n]

# 시간복잡도: O(n)
# 공간복잡도: O(n)

그리디 (Greedy):

# 예제: 동전 거스름돈
def coin_change(coins, amount):
    coins.sort(reverse=True)
    count = 0
    
    for coin in coins:
        if amount >= coin:
            count += amount // coin
            amount %= coin
    
    return count if amount == 0 else -1

2. 필수 자료구조

우선순위별 학습

우선순위자료구조출제 빈도난이도
1배열 (Array)매우 높음쉬움
2해시맵 (HashMap)매우 높음쉬움
3스택 (Stack)높음쉬움
4큐 (Queue)높음쉬움
5힙 (Heap)중간중간
6트리 (Tree)중간중간
7그래프 (Graph)중간어려움

자료구조별 핵심 연산

배열:

# 필수 연산
arr = [1, 2, 3, 4, 5]
arr.append(6)        # 뒤에 추가 O(1)
arr.pop()            # 뒤에서 제거 O(1)
arr.insert(0, 0)     # 앞에 추가 O(n)
arr.remove(3)        # 값 제거 O(n)
arr.sort()           # 정렬 O(n log n)

해시맵:

# 필수 연산
d = {}
d['key'] = 'value'   # 삽입 O(1)
val = d.get('key')   # 조회 O(1)
del d['key']         # 삭제 O(1)
'key' in d           # 존재 확인 O(1)

스택:

# 스택 (LIFO)
stack = []
stack.append(1)      # push
stack.append(2)
top = stack.pop()    # pop (2)

:

from collections import deque

# 큐 (FIFO)
queue = deque()
queue.append(1)      # enqueue
queue.append(2)
front = queue.popleft()  # dequeue (1)

:

import heapq

# 최소 힙
heap = []
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 2)

min_val = heapq.heappop(heap)  # 1

# 최대 힙 (음수 사용)
max_heap = []
heapq.heappush(max_heap, -3)
heapq.heappush(max_heap, -1)
max_val = -heapq.heappop(max_heap)  # 3

3. 문제 풀이 패턴

패턴 인식 플로우차트

flowchart TD
    A[문제 읽기] --> B{정렬된 배열?}
    B -->|예| C[이진 탐색 or 투 포인터]
    B -->|아니오| D{부분 배열/부분 문자열?}
    D -->|예| E[슬라이딩 윈도우]
    D -->|아니오| F{그래프/트리?}
    F -->|예| G[BFS/DFS]
    F -->|아니오| H{최적화 문제?}
    H -->|예| I[DP or 그리디]
    H -->|아니오| J[해시맵 or 스택/큐]

패턴별 예제

패턴 1: 투 포인터

# LeetCode 15. 3Sum
def three_sum(nums):
    nums.sort()
    result = []
    
    for i in range(len(nums) - 2):
        if i > 0 and nums[i] == nums[i-1]:
            continue
        
        left, right = i + 1, len(nums) - 1
        while left < right:
            total = nums[i] + nums[left] + nums[right]
            if total == 0:
                result.append([nums[i], nums[left], nums[right]])
                left += 1
                right -= 1
                while left < right and nums[left] == nums[left-1]:
                    left += 1
            elif total < 0:
                left += 1
            else:
                right -= 1
    
    return result

패턴 2: 슬라이딩 윈도우

# LeetCode 3. Longest Substring Without Repeating Characters
def length_of_longest_substring(s):
    char_set = set()
    left = 0
    max_length = 0
    
    for right in range(len(s)):
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        
        char_set.add(s[right])
        max_length = max(max_length, right - left + 1)
    
    return max_length

패턴 3: BFS

# LeetCode 102. Binary Tree Level Order Traversal
from collections import deque

def level_order(root):
    if not root:
        return []
    
    result = []
    queue = deque([root])
    
    while queue:
        level_size = len(queue)
        level = []
        
        for _ in range(level_size):
            node = queue.popleft()
            level.append(node.val)
            
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        
        result.append(level)
    
    return result

4. 시간 관리

시험 시간 배분 (2시간, 4문제 기준)

총 시간: 120분

문제 1 (Easy):   20분
문제 2 (Medium): 30분
문제 3 (Medium): 35분
문제 4 (Hard):   35분

예비 시간: 10분 (검토 및 디버깅)

문제 풀이 프로세스

flowchart LR
    A[문제 읽기 3분] --> B[예제 분석 2분]
    B --> C[접근법 설계 5분]
    C --> D[코드 작성 15분]
    D --> E[테스트 3분]
    E --> F[디버깅 2분]

각 단계별 체크리스트:

1. 문제 읽기 (3분):

  • 입력 형식 확인
  • 출력 형식 확인
  • 제약 조건 확인 (n의 범위, 시간 제한)
  • 엣지 케이스 파악

2. 예제 분석 (2분):

  • 예제 입력/출력 손으로 따라가기
  • 패턴 인식
  • 예외 케이스 생각

3. 접근법 설계 (5분):

  • 브루트포스 시간복잡도 계산
  • 최적화 방법 고민
  • 자료구조 선택
  • 슈도코드 작성

4. 코드 작성 (15분):

  • 함수 시그니처 작성
  • 핵심 로직 구현
  • 엣지 케이스 처리

5. 테스트 (3분):

  • 예제 입력으로 테스트
  • 엣지 케이스 테스트 (빈 배열, 크기 1, 최대 크기)
  • 출력 형식 확인

6. 디버깅 (2분):

  • 에러 메시지 확인
  • print 디버깅
  • 논리 오류 수정

5. 언어 선택

언어별 장단점

언어장점단점추천 대상
Python간결한 문법, 빠른 구현느린 속도 (TLE 가능)대부분의 경우
C++빠른 속도, STL긴 코드, 디버깅 어려움성능 중요 문제
Java안정적, 풍부한 API장황한 문법엔터프라이즈 배경
JavaScript웹 개발자 친숙타입 안전성 부족웹 개발자

Python 추천 이유

1. 간결한 문법:

# Python (5줄)
def reverse_string(s):
    return s[::-1]

# C++ (10줄)
#include <string>
#include <algorithm>
string reverseString(string s) {
    reverse(s.begin(), s.end());
    return s;
}

2. 풍부한 내장 함수:

# 정렬
arr.sort()

# 최대/최소
max_val = max(arr)
min_val = min(arr)

# 합계
total = sum(arr)

# 카운트
from collections import Counter
count = Counter(arr)

3. 슬라이싱:

arr = [1, 2, 3, 4, 5]
arr[1:4]    # [2, 3, 4]
arr[::-1]   # [5, 4, 3, 2, 1] (역순)
arr[::2]    # [1, 3, 5] (2칸씩)

C++ 사용 시기

TLE (Time Limit Exceeded) 발생 시:

문제 제약: n ≤ 10^6, 시간 제한 1초

Python: 10^6 연산 → 1초 (아슬아슬)
C++:    10^6 연산 → 0.1초 (여유)

→ Python으로 풀다가 TLE 나면 C++로 전환

6. 실전 팁

팁 1: 템플릿 준비

Python 템플릿:

# 입력 처리
n = int(input())
arr = list(map(int, input().split()))

# 또는
import sys
input = sys.stdin.readline

# 출력
print(result)

C++ 템플릿:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    vector<int> arr(n);
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }
    
    // 로직
    
    cout << result << endl;
    
    return 0;
}

팁 2: 시간복잡도 체크

n의 범위로 시간복잡도 추정:

n ≤ 10:        O(n!) 가능
n ≤ 20:        O(2^n) 가능
n ≤ 100:       O(n³) 가능
n ≤ 1,000:     O(n²) 가능
n ≤ 10,000:    O(n² / 2) 가능
n ≤ 100,000:   O(n log n) 필요
n ≤ 1,000,000: O(n) 필요
n > 1,000,000: O(log n) 또는 O(1) 필요

팁 3: 디버깅 전략

# print 디버깅
def solve(arr):
    print(f"DEBUG: arr = {arr}")  # 입력 확인
    
    result = []
    for i in range(len(arr)):
        print(f"DEBUG: i = {i}, arr[i] = {arr[i]}")  # 중간 과정
        result.append(arr[i] * 2)
    
    print(f"DEBUG: result = {result}")  # 출력 확인
    return result

팁 4: 엣지 케이스 체크

# 항상 테스트해야 할 케이스
test_cases = [
    [],              # 빈 배열
    [1],             # 크기 1
    [1, 1, 1],       # 모두 같은 값
    [1, 2, 3],       # 정렬된 배열
    [3, 2, 1],       # 역순
    [-1, 0, 1],      # 음수 포함
    [10**9],         # 최대값
]

7. 정리

3개월 학습 플랜

1개월차: 기초 다지기

  • 배열, 해시맵, 스택, 큐
  • 투 포인터, 슬라이딩 윈도우
  • LeetCode Easy 50문제

2개월차: 중급 알고리즘

  • BFS/DFS, 이진 탐색
  • 동적 프로그래밍 기초
  • LeetCode Medium 50문제

3개월차: 고급 알고리즘

  • 동적 프로그래밍 고급
  • 그래프 알고리즘
  • LeetCode Medium 50문제 + Hard 20문제

학습 자료

온라인 저지:

추천 문제집:

  • LeetCode Top 100 Liked
  • LeetCode Blind 75
  • 백준 단계별로 풀어보기

다음 단계

알고리즘별 자세한 설명은 아래 시리즈를 참고하세요:

  • 알고리즘 시리즈 #1: 배열과 리스트
  • 알고리즘 시리즈 #10: BFS/DFS
  • 알고리즘 시리즈 #12: 동적 프로그래밍

관련 주제:

  • 시간복잡도 최적화 체크리스트
  • 투 포인터 패턴
  • 슬라이딩 윈도우 패턴