코딩 테스트 완벽 대비 가이드 | 알고리즘부터 실전 팁까지
이 글의 핵심
투 포인터·슬라이딩 윈도우·이진 탐색·BFS/DFS부터 DP·그리디·최단경로·위상정렬·트라이까지, 왜 쓰는지와 흔한 실수까지 풀어 쓴 뒤 자료구조·패턴·시간 배분·언어 선택으로 이어집니다.
들어가며: 코딩 테스트는 준비가 전부
”알고리즘을 몰라서 떨어졌어요”
코딩 테스트는 패턴 인식입니다. 자주 나오는 패턴을 익히고, 반복 연습하면 누구나 통과할 수 있습니다. 다만 현장에서는 “이름”보다 제약 조건과 시간 복잡도가 먼저 말을 겁니다. n이 10만이면 O(n²)은 대개 버티기 어렵고, n이 20이면 지옥 같은 브루트포스도 허용되기도 해요. 그래서 알고리즘을 공부할 때는 “언제 쓰이고, 언제 틀리는지”를 같이 적어 두면 나중에 시험장에서 훨씬 덜 헤맙니다. 이 글에서 다루는 것:
- 필수 알고리즘 및 자료구조
- 문제 풀이 패턴
- 시간 관리 전략
- 언어 선택 가이드
- 실전 팁
코딩 테스트 준비하며 깨달은 것
알고리즘 문제를 풀다 보면 “이게 실무에 무슨 도움이 될까?” 하는 의문이 들 때가 있습니다. 저도 그랬습니다. 하지만 실제 프로젝트에서 성능 문제에 부딪히면, 알고리즘 지식이 얼마나 중요한지 깨닫게 됩니다. 예를 들어, 사용자 검색 기능이 느려서 고민하다가 해시 테이블을 적용하니 응답 시간이 10초에서 0.1초로 줄어든 경험이 있습니다. 코딩 테스트는 단순히 취업을 위한 관문이 아니라, 문제 해결 능력을 키우는 훈련장입니다. 처음엔 브루트 포스로 풀다가, 시간 복잡도를 개선하는 과정에서 “아, 이렇게 생각하면 되는구나” 하는 깨달음을 얻을 때의 쾌감은 말로 표현하기 어렵습니다. 이 글에서는 단순히 정답 코드만 제시하는 게 아니라, 문제를 어떻게 접근하고 최적화하는지 사고 과정을 함께 공유하겠습니다.
1. 필수 알고리즘
알고리즘 이름만 외우면 시험장에서 잘 안 떠오릅니다. 대신 “이 문제는 뭘 줄이려고 하는가?”를 머릿속에 그려 두는 편이 낫습니다. 배열을 두 번 도는 걸 한 번으로 줄이면 투 포인터나 슬라이딩 윈도우, 결정 문제(정답이 X 이상인가?)를 반복해서 물으면 이진 탐색, 연결 관계를 한 덩어리씩 관리하면 그래프 탐색 쪽을 떠올리면 됩니다. 아래 코드는 뼈대이고, 그 아래 글은 시험에서 실제로 판단할 때 쓰는 말입니다.
우선순위별 학습 순서
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단계: 필수 알고리즘 (출제 빈도 높음)
투 포인터
한 줄 요약: 정렬이 되어 있거나, 앞뒤에서 동시에 좁혀도 “후보가 갈리는” 구조일 때 쓰는 기법입니다. 이중 for문으로 O(n²) 나올 것을 O(n)으로 내리는 패턴이 꽤 많습니다.
시험에서 보통 이렇게 접근합니다. 합이 목표보다 작으면 왼쪽 포인터만 늘리면 합이 커지고, 크면 오른쪽만 줄이면 합이 작아집니다. 배열이 오름차순이라는 전제가 있으면 “이제 후보를 버리는 지점이 명확하다”는 뜻이에요. 반대로 정렬이 없는데 투 포인터만 억지로 쓰면 막히거나 증명이 안 됩니다. 그때는 해시로 역할을 나누거나, 먼저 정렬해도 되는지(인덱스가 중요한지)부터 봐야 합니다.
대표적인 함정은 두 가지입니다. 하나는 left < right와 left <= right를 헷갈리는 것(문제마다 “같은 원소 두 번 써도 되는지”가 다름). 다른 하나는 중복 제거(정렬 후 nums[i] == nums[i-1] 스킵 같은 처리)를 빼먹어서 시간 초과나 오답 나는 경우입니다.
# 예제: 정렬된 배열에서 두 수의 합
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)
슬라이딩 윈도우
한 줄 요약: “연속된 구간”의 합·개수·특성을 매번 처음부터 다시 계산하지 않고, 한 칸 밀 때 들어온 값과 나간 값만 반영합니다.
고정 길이 k면 구현이 단순합니다. 한 윈도우의 합을 구해 두고, 오른쪽으로 한 칸 갈 때 + arr[i] - arr[i-k]만 하면 됩니다. 가변 길이(조건을 만족하는 최소/최대 길이)는 오른쪽으로 늘리다가 조건이 깨지면 왼쪽을 당겨 맞추는 식으로 많이 풉니다. 이때 “왼쪽을 얼마나 당겨야 하는가?”가 문제의 핵심인 경우가 많고, 보통은 카운터(문자 개수, 조건 카운트)를 같이 들고 움직입니다.
투 포인터랑 겹쳐 보이는데, 슬라이딩 윈도우는 구간이 연속이라는 전제가 더 분명합니다. “부분 수열”처럼 연속이 아니면 이 패턴이 아닙니다.
# 예제: 길이 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)
이진 탐색
한 줄 요약: 정렬된 배열에서만 도는 게 아니라, “답이 X일 수 있나?”에 true/false가 단조로울 때 경계를 반으로 자르는 도구로 더 많이 씁니다.
코딩 테스트에서 제일 흔한 형태는 두 가지입니다. 하나는 전형적인 “있는지 찾기”(위 코드). 다른 하나는 lower_bound 류: “target 이상이 처음 나오는 위치”나 “조건을 만족하는 최소/최대 값”을 찾는 것입니다. 후자는 while left < right에 mid 계산을 (left + right) // 2로 할지 올림으로 할지에 따라 하루 종일 헷갈립니다. 여기서는 원칙만 짚을게요: 루프 불변식(left/right가 답이 들어갈 구간의 양 끝을 나타낸다)을 종이에 한 줄 적어 두고 짜면 사고 시간이 확 줄어요.
실수 포인트는 mid = (left + right) // 2에서 큰 정수일 때 오버플로가 나는 언어(C++/Java 등)는 left + (right - left) // 2 습관이 안전합니다. Python은 덜하지만, 개념은 같습니다.
# 예제: 정렬된 배열에서 값 찾기
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
한 줄 요약: 둘 다 “그래프나 트리를 한 번씩 질서 있게 도는 방법”인데, 큐를 쓰느냐 스택(재귀)을 쓰느냐 차이가 거의 전부입니다.
BFS는 가까운 쪽부터 퍼집니다. 그래서 미로에서 최단 거리(가중치 없을 때), 트리에서 레벨별 순회, 상태 공간이 넓은데 깊이 제한이 명확한 문제에 잘 맞습니다. DFS는 한 길을 끝까지 파고들었다가 되돌아옵니다. 모든 경로/부분집합을 조사하거나, 백트래킹과 같이 “선택했다가 취소”하는 구조와 잘 맞습니다.
시험에서 DFS 재귀가 깊어지면(최악 수만 오) 재귀 한도에 걸릴 수 있습니다. 그때는 명시적 스택으로 DFS를 구현하거나, BFS로 바꿀 수 있는지 보면 됩니다. visited를 안 하면 순환 그래프에서 무한 루프 나는 건 기본 중의 기본입니다.
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)
한 줄 요약: 큰 문제를 작은 문제의 답으로 쌓아 올립니다. “겹치는 하위 문제”가 있고, 최적 부분 구조(작은 답을 알면 큰 답을 만들 수 있음)가 성립할 때 씁니다.
피보나치 예제는 설명용이고, 시험에서는 보통 배낭, LIS, 편집 거리, 구간 합 조건 같은 형태로 나옵니다. 설계할 때는 dp[i]가 정확히 무엇을 의미하는지 한국어로 한 문장 적는 습관이 중요해요. “앞 i개까지 봤을 때 …”처럼요. 메모이제이션(재귀+캐시)과 바텀업(표 채우기)은 같은 수학이고, 후자가 디버깅이 쉬운 편입니다.
백트래킹은 일단 후보를 탐색하다 막히면 되돌아오는 전략이고, DP와 같이 쓰이기도(가지치기) 합니다. “모든 경우”를 다 보면 시간이 터지니, 제약이 작을 때(n ≤ 20 등) 많이 나옵니다.
# 예제: 피보나치 수열
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)
그리디
한 줄 요약: 매 단계에서 지금 보기에 제일 좋은 선택을 하면 전체가 최적이라는 교환 논증이 있을 때만 안전합니다. 아래 동전 예제는 동전 액면이 서로 배수 관계일 때(예: 1, 5, 10, 50원) 같은 특수한 구조에서 잘 작동하는 패턴입니다. 임의의 동전 조합이면 그리디가 틀릴 수 있어요. 시험에서 그리디를 택했다면 “왜 이 순서가 맞는지”를 스스로에게 한 번 말해 보는 게 좋습니다. 안 되면 그리디가 아니라 DP나 완탐+최적화일 가능성을 의심합니다.
# 예제: 동전 거스름돈
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
3단계: 고급 알고리즘 (가끔, 그러나 한 번 나오면 크게 다침)
여기는 매 라운드마다 필수는 아닙니다. 다만 “그래프 + 가중치”, “선행 과목/작업 순서”, “문자열을 접두어로 많이 검색” 같은 문장이 보이면 머리에 올려두면 편합니다. 최단 경로: 간선에 가중치가 있으면 다익스트라(음수 없을 때), 음수 간선이면 벨만-포드, 모든 쌍이면 플로이드-워샬 같은 선택지가 나옵니다. 코테에서는 다익스트라+우선순위 큐 조합이 제일 흔한 편입니다. “간선이 많은지 적은지”에 따라 인접 리스트를 쓰는 건 당연 전제고요. 위상 정렬: 방향 그래프에서 사이클이 없고, 선행 관계를 한 줄로 펴야 할 때 씁니다. indegree(들어오는 간선 수)를 큐로 0인 애부터 빼 내는 방식이 직관적입니다. 사이클이 있으면 모든 정점을 방문 못 하니까 그걸로 “불가능” 판정하는 문제도 많습니다. 트라이(Trie): 문자열의 접두어 공유를 트리로 표현합니다. 자동완성, 검색어 중복 제거, 비트 트라이로 XOR 최대 같은 변형문까지 이어집니다. 구현은 노드 맵이나 고정 크기 자식 배열 두 가지 흐름이 대표적이에요.
2. 필수 자료구조
자료구조는 “암기 목록”이라기보다 연산의 평균 비용을 손에 익히는 일에 가깝습니다. 코테에서 시간 초과가 나면 대부분은 알고리즘 자체보다 “이 연산이 O(n)인 줄 알았는데 O(n log n)” 같은 선택 실수에서 옵니다. 아래 표는 대략적인 출제 빈도 느낌이고, 본인이 약한 연산만 표시해 두고 문제 풀 때마다 한 번씩 짚어도 충분합니다.
우선순위별 학습
| 우선순위 | 자료구조 | 출제 빈도 | 난이도 |
|---|---|---|---|
| 1 | 배열 (Array) | 매우 높음 | 쉬움 |
| 2 | 해시맵 (HashMap) | 매우 높음 | 쉬움 |
| 3 | 스택 (Stack) | 높음 | 쉬움 |
| 4 | 큐 (Queue) | 높음 | 쉬움 |
| 5 | 힙 (Heap) | 중간 | 중간 |
| 6 | 트리 (Tree) | 중간 | 중간 |
| 7 | 그래프 (Graph) | 중간 | 어려움 |
자료구조별 핵심 연산
배열
리스트는 “그냥 쓰는 것” 같아도, 앞쪽 삽입/삭제가 잦은지, 정렬 후 이진 탐색까지 갈지 plan을 세우면 난이도가 갈립니다. Python 리스트는 끝이 싸고 앞이 비쌉니다. C++ vector도 비슷한 감각이에요. “정렬했다가 인덱스가 필요하다”면 원본 인덱스를 pair로 들고 다니는 패턴을 한 번쯤 손으로 써 보세요.
# 필수 연산
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)
해시맵 (딕셔너리)
“값 → 빈도”, “값 → 최근 인덱스”, “필요한 보조 정보 O(1)로” 같은 문제의 기본기입니다. 키로 리스트나 커스텀 객체를 쓸 때는 해시 가능 여부(Python은 tuple은 되고 list는 안 됨)만 확인하면 됩니다. 정렬이 필요한데 dict만 맴돌면 역으로 느려질 때가 있어요. “카운트만”이면 Counter가 읽기 쉽습니다.
# 필수 연산
# 실행 예제
d = {}
d['key'] = 'value' # 삽입 O(1)
val = d.get('key') # 조회 O(1)
del d['key'] # 삭제 O(1)
'key' in d # 존재 확인 O(1)
스택
가장 나중에 넣은 게 먼저 나옴이 생명입니다. 괄호 짝 맞추기, 트리 DFS를 반복문으로, “되돌리기”가 있는 문제에서 자주 등장합니다. 큐와 헷갈리면 “대기줄 vs 접시 쌓기”로만 구분해도 시험장에서 덜 섞입니다.
# 스택 (LIFO)
# 실행 예제
stack = []
stack.append(1) # push
stack.append(2)
top = stack.pop() # pop (2)
큐
BFS의 큐는 deque가 표준입니다. 리스트로 pop(0) 하면 O(n)이라 조용히 터집니다. “먼저 들어온 게 먼저 처리”만 기억하면 되고, 우선순위가 생기는 순간 그건 큐가 아니라 힙 문제로 넘어갑니다.
from collections import deque
# 큐 (FIFO)
queue = deque()
queue.append(1) # enqueue
queue.append(2)
front = queue.popleft() # dequeue (1)
힙 (우선순위 큐)
파이썬 heapq는 최소 힙이라 “최대값 꺼내기”는 음수 트릭이나 (우선순위, 카운트, 값) 튜플로 tie-break를 고정하는 방식이 흔합니다. 다익스트라, 중간값 유지(두 힙), 스케줄링 문제에서 부담 없이 쓰입니다. “정렬을 매번” 할 거를 힙 한 번으로 줄이는 그림인지 보면 설계가 빨라져요.
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
트리와 그래프 (개념만 짚기)
코테에서 트리는 보통 포인터(노드 객체)로 주거나 부모 배열/인접 리스트로 줍니다. 재귀 DFS가 읽기 쉽고, 깊이가 깊으면 스택으로 바꿉니다. 그래프는 무방향/방향, 가중치 유무, 자기 루프·중복 간선만 먼저 체크해도 구현 실수가 확 줄어요.
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 스택/큐]
패턴별 예제
아래 LeetCode 번호는 “이런 류였지” 되살리기용입니다. 원리가 이해되면 같은 계열 사이트 문제로 옮겨도 됩니다.
패턴 1: 투 포인터 — 정렬 후 좌우를 좁혀 가며 중복·합 조건을 만족하는 튜플을 모읍니다. 고정된 i에 대해 left/right가 한 번씩만 지나가도록 짜는 게 핵심이에요.
# 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: 슬라이딩 윈도우 — 오른쪽을 전진시키면서 조건을 깨면 왼쪽을 당깁니다. set 대신 카운터 딕셔너리로 바꾸는 변형이 더 자주 나옵니다(문자 빈도 제한 문제).
# 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 (레벨 순회) — 큐에서 한 레벨씩 level_size로 묶으면 “깊이별 결과”를 바로 얻습니다. 최단 거리(가중치 1) 문제에서도 같은 그림이 반복됩니다.
# 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. 시간 관리
시간 배분표는 사람마다 체감이 달라서, 숫자보다 “막히면 바로 다음으로” 같은 규칙을 하나 정해 두는 편이 실전에 도움이 됩니다. 아래 표는 4문제짜리 2시간 전형을 가정한 예시입니다.
시험 시간 배분 (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으로만 연습했는데 시험이 C++만 허용이면 당연히 불리하고, 반대로 회사 스택이 Java면 IDE 자동완성에 익숙한 쪽이 유리할 수도 있어요. 아래 표는 대략적인 성향만 보고, 마지막 한 달은 실제 시험과 같은 환경으로 맞추는 게 좋습니다.
언어별 장단점
| 언어 | 장점 | 단점 | 추천 대상 |
|---|---|---|---|
| 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. 실전 팁
여기서부터는 “알고리즘”이라기보다 시험 볼 때 신경 덜 쓰게 만드는 습관에 가깝습니다. 입력/출력 템플릿은 손이 기억하게 만들고, 복잡도 표는 n 범위만 봐도 대충 감이 오게 붙여 두세요.
팁 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 - 글로벌 표준
- 백준 - 한국 최대
- 프로그래머스 - 기업 코딩 테스트 추천 문제집:
- LeetCode Top 100 Liked
- LeetCode Blind 75
- 백준 단계별로 풀어보기
다음 단계
같은 사이트에서 풀이를 더 깊게 보고 싶다면 아래를 순서 없이 골라도 됩니다. 번호가 작다고 쉬운 건 아니니, 목차만 보고 필요한 조각부터 열어도 괜찮아요.
- 알고리즘 시리즈 #1: 배열과 리스트
- 알고리즘 시리즈 #9: 이진 탐색
- 알고리즘 시리즈 #10: BFS/DFS
- 알고리즘 시리즈 #12: 동적 프로그래밍
- 투 포인터·슬라이딩 윈도우 (LeetCode 스타일) 관련 주제:
- 시간복잡도 최적화 체크리스트
- 투 포인터 패턴
- 슬라이딩 윈도우 패턴
- 그리디 알고리즘 시리즈
- 백트래킹
- 기술 블로그로 풀이·노트를 자산화하기
- 개발 취업 실전 팁 (코테·과제·면접 흐름)
- 개발자 이력서·서류·면접 가이드
- 개발자 채용 공고 사이트 가이드
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「코딩 테스트 완벽 대비 가이드 | 알고리즘부터 실전 팁까지」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「코딩 테스트 완벽 대비 가이드 | 알고리즘부터 실전 팁까지」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. 투 포인터·슬라이딩 윈도우·이진 탐색·그래프 탐색부터 DP·그리디·최단경로·위상정렬·트라이까지 개념과 실수 포인트를 풀어 쓰고, 자료구조·패턴·시간 배분·언어 선택으로 이어지는 코딩 테스트 준비 글입니다. Start… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- LeetCode 패턴: 두 포인터와 슬라이딩 윈도우 | 템플릿과 C++/Python
- 배열과 리스트 | 코딩 테스트 필수 자료구조 완벽 정리
- 투 포인터 | O(n²) → O(n) 최적화 기법 완벽 정리
이 글에서 다루는 키워드 (관련 검색어)
코딩테스트, 알고리즘, 자료구조, LeetCode, 백준, 프로그래머스, 면접 등으로 검색하시면 이 글이 도움이 됩니다.