F-3: 코딩테스트 실전 — 배운 걸 제한 시간 안에 꺼내 쓰는 법
목차 29
안녕하세요, 여러분의 알고리즘 길잡이 홍순구 튜터입니다. 드디어 이 과목의 마지막 시간입니다.
지난 시간(F-2)까지 우리는 스무 개의 모듈을 걸어왔습니다. 빅오로 코드의 속도를 읽는 눈에서 출발해, 배열과 연결 리스트, 스택과 큐, 해시와 트리와 힙, 정렬과 이진 탐색, 재귀와 완전탐색, 그리디와 동적 계획법, 그리고 다익스트라부터 유니온 파인드·위상 정렬·최소 신장 트리까지. 자료구조를 손으로 짜 보고, 알고리즘이 도는 과정을 눈으로 따라가고, 그때마다 "이게 시간 안에 들어오나"를 되물었죠.
그런데 이 모든 걸 배웠어도, 코딩테스트 시험장에 앉으면 벽에 부딪히는 사람이 많습니다. 아는 것과 제한 시간 안에 꺼내 쓰는 것은 다른 능력이거든요. 지문을 읽고 "아, 이건 그때 배운 그거네" 하고 알아채는 데 걸리는 3분, 맞는 풀이인데 시간 초과로 떨어지는 마지막 한 줄, 예제는 다 맞는데 채점에서 무너지는 경계 하나. 오늘은 스무 개의 무기를 실제 전장에서 어떻게 뽑아 드는지를 다룹니다.
지난 시간 마지막에 세 가지를 예고했었죠. 지문의 키워드로 알고리즘을 읽는 법 — "사이클이 생기지 않게"가 보이면 유니온 파인드, "순서"나 "선행 조건"이면 위상 정렬, "전부 잇는 최소 비용"이면 최소 신장 트리요. 그리고 입출력이 시간을 잡아먹는 문제, 마지막으로 자주 틀리는 함정. 오늘 이 셋을 전부 갚습니다. 하나씩요.
오늘의 여정 — 배운 스무 개를 실전에서 꺼내 쓴다
[1] 입력 크기부터 본다 n을 보고 허용 복잡도를 거꾸로 읽기
| ("1초 1억" — A-1의 그 표를 마지막으로 꺼낸다)
v
[2] 키워드로 알고리즘 읽기 "사이클"→유니온 파인드 · "순서"→위상 정렬
| 지문의 말이 곧 알고리즘을 가리킨다
v
[3] 분별 훈련 짧은 지문 여섯 개를 유형으로 맞혀 본다
|
v
[4] 출력도 병목이 된다 print를 줄마다 부르지 않고 모아서 한 번에
| 입력은 지난 A-2에서, 출력은 오늘
v
[5] 함정 ① 재귀 깊이 깊이가 n이면 터진다 — 진단하고 갈아탄다
|
v
[6] 함정 ② 경계·예외 입력 빈 입력·n=1·중복에서만 무너지는 코드
|
v
[7] 시간 배분 세 문제에 두 시간, 어떻게 나누나
|
v
[8] 시험 당일 + 과목을 닫으며
A-1의 첫 시간, 제가 여러분께 "시간 초과" 화면을 보여 드리며 물었던 것 기억하시나요. "정답은 맞는데 왜 떨어질까요?" 오늘 우리는 그 화면을 미리 읽어 내는 사람이 되어 이 과목을 닫습니다.
💡 오늘 수업의 핵심 — "아는 것과 시간 안에 꺼내 쓰는 것은 다른 능력이다 — 입력 크기로 복잡도를 정하고, 키워드로 알고리즘을 고르고, 입출력과 경계 함정을 피해 통과한다"
🎯 학습 목표
- 문제의 입력 크기 n을 보고 허용 복잡도를 거꾸로 읽어, 풀이를 짜기 전에 시간 초과를 미리 진단합니다.
- 지문의 키워드로 알고리즘을 분별하고, 배운 스무 개 중 무엇을 꺼낼지 후보를 좁히는 판단표를 세웁니다.
- 입출력 최적화(출력 누적)와 자주 틀리는 함정(재귀 깊이·경계·예외 입력)을 실제 코드로 확인하고, 시간 배분과 시험 당일 전략으로 마무리합니다.
Step 1: "입력 크기부터 본다" — n을 보고 복잡도를 거꾸로 읽기 (~20분)
문제를 받으면 가장 먼저 무엇을 볼까요? 많은 분이 곧장 풀이부터 떠올립니다. 그런데 실전에 익숙한 사람은 그 전에 딱 한 줄을 찾습니다. 입력 크기 제한이요.
1 ≤ n ≤ 100,000 같은 한 줄이 문제 어딘가에 반드시 적혀 있습니다. 이게 출제자가 몰래 남긴 쪽지예요. "이 크기까지 들어올 테니, 이 정도 속도로 풀어라"라는 힌트가 숫자에 숨어 있거든요. 지도의 축척을 먼저 봐야 거리를 가늠하듯, 입력 제한을 먼저 봐야 어떤 풀이가 통과할지 가늠됩니다.
A-1 첫 시간에 세운 "1초 ≈ 1억 연산" 잣대, 기억하시죠. 그 표를 이 과목 마지막 시간에 다시 꺼냅니다. 다만 이번엔 방향이 반대예요. 그때는 "내 풀이가 O(n²)인데 통과할까?"를 물었다면, 실전에서는 "n이 이만큼인데 어떤 복잡도까지 허용되나?"를 먼저 묻습니다. 답을 정해 놓고 되짚는 거죠.
복잡도별 '1초 안에 감당하는 n의 상한' (실제 계산값)
O(n!) n ≈ 11 순열 완전탐색은 원소 11개까지
O(2ⁿ) n ≈ 26 부분집합 완전탐색은 26개까지
O(n²) n ≈ 10,000 이중 루프는 1만 개까지
O(n log n) n ≈ 4,545,454 정렬·힙·이분 탐색은 450만까지
O(n) n ≈ 100,000,000 한 번 훑기는 1억까지
O(log n)·O(1) 사실상 제한 없음
이 판단을 눈대중이 아니라 코드로 굳혀 두면, 시험장에서 헷갈릴 일이 없습니다. A-1에서 만든 도구를 그대로 다시 꺼내 봅시다. 입력 크기 n을 주면, 1초 안에 드는 복잡도 계급만 골라 돌려주는 함수예요.
# complexity/tle_estimator.py (A-1에서 만든 도구를 실전에서 다시 꺼낸다)
def feasible_complexities(n, seconds=1):
"""주어진 n에서 제한 시간 안에 드는 복잡도 계급만 추려 돌려준다.
예) n = 100,000 이면 O(n^2)은 빠지고 O(n log n) 이하만 남는다.
"""
classes = ["O(1)", "O(log n)", "O(n)", "O(n log n)", "O(n^2)", "O(2^n)", "O(n!)"]
return [c for c in classes if fits_in_time(c, n, seconds)]
세 가지 입력 크기를 넣어 보면 결과가 이렇게 갈립니다.
feasible_complexities(1,000) → ['O(1)', 'O(log n)', 'O(n)', 'O(n log n)', 'O(n^2)']
feasible_complexities(100,000) → ['O(1)', 'O(log n)', 'O(n)', 'O(n log n)']
feasible_complexities(10,000,000) → ['O(1)', 'O(log n)', 'O(n)']
핵심은 가운데 줄입니다. n ≤ 100,000이 보이는 순간, O(n²)는 목록에서 빠져요. 1만 곱하기 1만이 1억인데, 10만 곱하기 10만은 100억이라 1초를 훌쩍 넘기거든요. 그러니 "O(n log n) 이하로 풀어야 한다"가 곧장 읽혀야 합니다. 이 한 줄 판단이 모든 풀이 설계의 출발점이에요.
그래서 A-1에서 굳혔던 시간 초과 진단 습관을 실전 버전으로 다시 새깁니다. 풀이를 짜기 전에 매번 이 네 걸음을 밟으세요.
- 문제의 입력 제한 n을 찾는다 (
n ≤ ?). - 위 표로 허용 복잡도 상한을 읽는다 (예:
n ≤ 10⁵→O(n log n)이하). - 내가 떠올린 풀이의 빅오를 읽는다.
- 상한을 넘으면 더 빠른 접근으로 갈아탄다. 범위 안이면 자신 있게 제출한다.
이 순서가 몸에 붙으면, "일단 짜 보고 시간 초과가 나면 그때 고민"하는 습관에서 벗어납니다. 짜기 전에 통과 여부를 미리 아는 거죠.
💡 한 줄 정리
문제를 받으면 풀이보다 입력 제한 n을 먼저 본다. "1초 1억" 표를 거꾸로 읽어 허용 복잡도를 정하고, 그 안에 드는 풀이만 짠다.
🙋 학생 질문 — "튜터님, n 제한이 안 적힌 문제도 있던데요?"
좋은 관찰이에요. 제한이 명시 안 된 문제도 가끔 있습니다. 그럴 땐 두 가지로 가늠해요. 첫째, 예제 입력의 크기를 봅니다. 예제가 원소 몇 개짜리인지가 힌트가 되거든요. 둘째, 제한 시간과 메모리를 봅니다. 시간이 2초로 넉넉하거나 메모리가 512MB로 크면 그만큼 여유가 있다는 신호예요.
그래도 애매하면 가장 안전한 쪽(더 낮은 복잡도)으로 짭니다. O(n²)와 O(n log n) 사이에서 고민된다면 O(n log n)으로 가는 게 안전하죠. 넘치는 건 시간 초과지만, 남는 건 그냥 빨리 끝나는 것뿐이니까요.
Step 2: "키워드로 알고리즘을 읽는다" — 지문이 정답을 흘린다 (~20분)
지난 시간(F-2) 마지막에 예고한 첫 번째 약속을 지킬 차례입니다. 지문의 키워드로 알고리즘을 분별하는 법이요.
실전에서 시간을 가장 많이 잡아먹는 건 구현이 아니라 "무엇을 꺼낼지 정하는 3분"입니다. 그런데 다행히, 출제자는 대개 지문에 실마리를 흘려 둬요. 탐정이 현장의 단서를 읽어 범인을 좁히듯, 우리는 지문의 표현을 읽어 알고리즘을 좁힙니다. "가장 짧은", "전부 잇는", "순서", "몇 가지 방법" 같은 말들이 각각 특정 알고리즘을 가리키거든요.
F-1 Step 7에서 우리는 최단 경로 세 알고리즘(다익스트라·벨만-포드·플로이드-워셜)을 고르는 판단표를 이미 만들었습니다. 오늘은 그 표를 배운 스무 개 전체로 넓힙니다. 지문에 이런 말이 보이면 이 알고리즘을 떠올리세요.
| 지문에 이런 말이 보이면 | 떠올릴 것 | 배운 곳 |
|---|---|---|
| "정렬된 배열에서", "정렬한 뒤" | 정렬 → 이분 탐색 | D-1·D-3 |
| "~을 최대로 하는 최소", "가능한지 판정" | 매개변수 탐색(이분) | D-3 |
| "연속한 부분 배열", "구간 합" | 투 포인터·슬라이딩·누적합 | D-4 |
| "가능한 모든 경우", "조합", "순열" (n이 작음) | 완전탐색·백트래킹 | E-1 |
| "매 순간 최선", "가장 큰 것부터" | 그리디 | E-3 |
| "경우의 수", "최대 이익", 겹치는 부분 문제 | 동적 계획법 | E-4·E-5 |
| "최소 비용 경로", "가장 빠른" + 가중치 | 최단 경로 (F-1 판단표로) | F-1 |
| "같은 무리인지", "사이클이 생기는지" | 유니온 파인드 | F-2 |
| "순서", "선행 조건", "먼저 들어야" | 위상 정렬 | F-2 |
| "전부 잇는 최소 비용", "모두 연결" | 최소 신장 트리 | F-2 |
이 표를 코드로 굳혀 두면, "이 지문엔 어떤 후보가 있나"를 기계적으로 훑을 수 있습니다. 실전에서 직접 돌리는 도구는 아니지만, 표를 손에 익히는 훈련용으로 좋아요. 규칙 목록을 이렇게 담아 뒀습니다.
# patterns/problem_classifier.py
# 지문에 이런 말이 보이면 이 알고리즘을 떠올린다. (실마리 여러 개 → 후보 하나)
KEYWORD_RULES = [
(("사이클", "같은 그룹", "연결되어 있는지", "같은 무리"), "유니온 파인드", "F-2",
"분리 집합·사이클 판정"),
(("순서", "선행", "먼저 들어야", "의존"), "위상 정렬", "F-2",
"방향 그래프의 줄 세우기 (DAG)"),
(("전부 잇는", "모두 연결", "최소 스패닝", "섬 연결"), "MST(크루스칼/프림)", "F-2",
"모든 정점을 잇는 최소 비용 트리"),
# ... (배운 A~F 범위의 규칙이 이어진다)
]
그리고 지문을 훑어 후보를 추리는 함수는 이렇게 단순합니다. 규칙마다 실마리 하나라도 지문에 있으면 후보로 담는 게 전부예요.
# patterns/problem_classifier.py
def match_keywords(text):
"""지문 문자열에서 실마리를 훑어 알고리즘 후보 목록을 돌려준다.
시간 O(R·K·m) (규칙 R개 × 규칙당 실마리 K개 × 지문 길이 m의 부분 문자열 검사)
· 공간 O(R) (후보 수는 규칙 수를 넘지 않는다)
"""
hits = []
for keywords, algorithm, module, clue in KEYWORD_RULES:
if any(kw in text for kw in keywords):
hits.append(Suggestion(algorithm, module, clue))
return hits
여기서 중요한 점 하나. 이 도구는 정답을 확정하지 않습니다. 후보를 여럿 돌려줄 뿐이에요. 실전 지문은 실마리가 섞여 있어서 후보가 둘 이상 나오는 게 정상이고, 그중 무엇을 먼저 시도할지는 사람이 입력 크기와 제약으로 좁힙니다. Step 1의 "입력 크기 보기"와 Step 2의 "키워드 읽기"가 짝을 이루는 이유가 이겁니다. 키워드로 후보를 모으고, 크기로 후보를 거르죠.
💡 한 줄 정리
출제자는 지문에 실마리를 흘린다. "사이클"→유니온 파인드, "순서"→위상 정렬, "전부 잇기"→최소 신장 트리처럼, 지문의 말을 알고리즘으로 옮기는 판단표를 손에 익힌다.
🙋 학생 질문 — "튜터님, 후보가 여러 개 나오면 오히려 더 헷갈리지 않나요?"
처음엔 그렇게 느껴질 수 있어요. 그런데 후보가 여럿인 게 오히려 정상이고, 안전합니다. 하나로 딱 찍어 주는 도구는 그 하나가 틀렸을 때 다른 길을 막아 버리거든요.
후보가 둘 이상이면 이렇게 좁힙니다. 첫째, 입력 크기로 거릅니다 (Step 1). "모든 경우"라는 말에 완전탐색과 DP가 둘 다 떠올랐는데 n이 100,000이면, 완전탐색(2ⁿ)은 탈락이고 DP만 남죠. 둘째, 제약 조건을 봅니다. 그래프에 음수 간선이 있으면 다익스트라가 빠지는 것처럼요. 후보를 모아 놓고 하나씩 지워 가는 게, 처음부터 하나만 붙잡는 것보다 훨씬 덜 위험합니다.
Step 3: "지문 여섯 개를 맞혀 본다" — 키워드와 크기를 함께 (~20분)
이론은 충분히 봤으니 이제 손을 움직여 봅시다. Step 1(크기 보기)과 Step 2(키워드 읽기)를 하나로 묶은 도구가 classify예요. 지문과 입력 크기를 함께 주면, 키워드로 후보를 모으고 크기로 조언을 붙여 돌려줍니다.
# patterns/problem_classifier.py
def classify(text, n=None):
"""지문과 (선택) 입력 크기로 후보 알고리즘과 크기 조언을 함께 돌려준다.
시간 O(R·K·m) · 공간 O(R)
이 함수의 결과는 '확정'이 아니라 '후보와 힌트'다. candidates가 비어 있으면
익숙한 실마리가 없다는 뜻이니, 구현·시뮬레이션(A-2)이나 완전탐색부터 의심한다.
"""
result = {"candidates": match_keywords(text)}
if n is not None:
note, allowed = size_hint(n)
result["size_note"] = note
result["feasible_complexities"] = allowed
return result
이제 지문 여섯 개를 실제로 넣어 봅시다. 여러분도 각 지문을 읽고 "무슨 알고리즘일까"를 먼저 떠올린 뒤, 도구의 답과 맞춰 보세요.
① "물류창고 n개, 두 지점 사이 최소 비용으로 물건 옮기기" (n=100,000)
→ 다익스트라(F-1) · O(n²)는 시간 초과 → O(n log n) 이하로
② "과목을 어떤 순서로 들어야 하는지, 선행 관계가 있다" (n=2,000)
→ 위상 정렬(F-2) · O(n²)까지 허용 → 이중 루프도 통과
③ "친구 관계로 몇 개의 무리로 나뉘는지, 사이클이 생기는지" (n=200,000)
→ 유니온 파인드(F-2) · O(n²)는 시간 초과 → O(n log n) 이하로
④ "모든 섬을 전부 잇는 최소 비용 다리" (n=100,000)
→ MST(크루스칼/프림)(F-2) · O(n log n) 이하로
⑤ "가능한 모든 조합을 살펴 최대 이익" (n=18)
→ 백트래킹/완전탐색(E-1), 동적 계획법(E-4) · n이 작다 → 2ⁿ 완전탐색 OK
⑥ "연속한 부분 배열의 구간 합이 목표 이상인 최소 길이" (n=1,000,000)
→ 투 포인터/슬라이딩 윈도우/누적합(D-4) · O(n log n) 이하로
몇 가지 짚어 볼게요. ②번은 n이 2,000이라 작습니다. 위상 정렬은 O(V+E)라 넉넉히 통과하지만, 크기가 작으니 설령 O(n²) 풀이가 떠올라도 괜찮다는 여유가 생기죠. 반대로 ③·⑥번은 n이 수십만~백만이라 O(n²) 계열은 손도 못 댑니다. 같은 유형이라도 크기에 따라 허용선이 완전히 달라지는 게 보이시나요.
⑤번이 특히 재미있어요. "모든 조합"이라는 말에 완전탐색이, "최대 이익"이라는 말에 동적 계획법이 둘 다 걸려서 후보가 두 개 나왔습니다. 그런데 n이 18이라 2¹⁸은 26만쯤이니 완전탐색이 1초 안에 넉넉히 들어와요. 이럴 땐 구현이 더 간단한 완전탐색을 먼저 시도하는 게 실전 감각입니다. 만약 n이 100,000이었다면 완전탐색은 탈락이고 DP만 남았겠죠.
이게 오늘 분별의 전부입니다. 키워드로 후보를 모으고, 크기로 후보를 거른다. 이 두 걸음이면 "무엇을 꺼낼지 정하는 3분"이 30초로 줄어듭니다.
💡 한 줄 정리
키워드로 후보를 모으고(Step 2), 입력 크기로 후보를 거른다(Step 1). 후보가 여럿이면 크기가 작은 쪽은 구현이 간단한 완전탐색을, 큰 쪽은 더 빠른 알고리즘을 고른다.
🙋 학생 질문 — "튜터님, 아무 후보도 안 나오면 어떡하죠?"
실제로 자주 있는 일이에요. 익숙한 키워드가 하나도 안 걸리는 지문이요. 그럴 땐 두 가지를 의심합니다.
첫째, 구현·시뮬레이션 문제일 가능성입니다. A-2에서 배운 것처럼, 특별한 알고리즘 없이 지문에 적힌 규칙을 그대로 코드로 옮기면 되는 유형이요. 격자를 돌거나, 조건대로 상태를 바꿔 가는 문제들이 여기 속합니다. 코딩테스트의 상당수가 사실 이런 구현 문제예요.
둘째, 완전탐색입니다. 마땅한 알고리즘이 안 보이고 n이 작다면(Step 1의 표에서 2ⁿ이나 n!이 허용되면), 가능한 경우를 전부 뒤지는 게 정답일 때가 많습니다. "모르겠으면 일단 크기를 보고, 작으면 완전탐색"이 실전의 든든한 기본기예요.
Step 4: "출력도 병목이 된다" — 모아서 한 번에 (~20분)
지난 시간 예고한 두 번째 약속, 입출력 최적화입니다.
A-2에서 우리는 들어오는 쪽을 빠르게 했었죠. 입력이 수십만 줄이면 input()이 느려서 시간 초과가 나니, sys.stdin.readline으로 바꾼다고요. 그런데 같은 문제가 나가는 쪽에도 똑같이 생깁니다. 답이 수십만 줄이면, 반복문 안에서 print()를 그만큼 부르게 되는데, 그 호출 하나하나의 부담이 쌓여 "풀이는 맞는데 시간 초과"가 다시 나요.
수도꼭지를 생각해 보세요. 물 한 컵을 백만 번 나르는 것과, 큰 통에 다 받아 한 번에 붓는 것. 옮기는 물의 양은 같지만, 왔다 갔다 하는 횟수가 다르죠. print()를 백만 번 부르는 게 앞쪽이고, 답을 모아 한 번에 내보내는 게 뒤쪽입니다.
먼저 학생들이 가장 먼저 쓰는 형태, print() 반복입니다.
# patterns/fast_io.py
def write_lines_with_print(values, out):
"""값들을 한 줄에 하나씩, print()를 값마다 한 번씩 호출해 내보낸다.
학생이 가장 먼저 쓰는 형태이자 대량 출력에서 시간 초과를 부르는 쪽이다.
시간 O(n), 공간 O(1)(모아 두지 않는다). 빅오는 아래 write_lines와 똑같지만
print() 호출당 상수가 커서 실측이 벌어진다.
"""
for value in values:
print(value, file=out)
그리고 실전 코테의 표준, 모아서 한 번에 내보내는 형태입니다. 답을 리스트에 모은 뒤 '\n'.join으로 한 문자열을 만들어 딱 한 번만 출력해요.
# patterns/fast_io.py
def write_lines(values, write):
"""값들을 한 줄에 하나씩, 모아서 write 한 번으로 내보낸다 — 실전 코테 표준.
공짜는 아니다. 출력 전체를 메모리에 들고 있으므로 공간이 O(1)에서
O(전체 출력 길이)로 는다. 시간을 사고 공간을 내주는 맞바꿈이다.
"""
write("\n".join(map(str, values)) + "\n")
그럼 실제로 얼마나 차이가 날까요? 두 방식으로 같은 개수를 내보내며 시간을 재 봤습니다. 화면에 직접 찍으면 터미널이 그리는 비용이 섞여 측정이 왜곡되니, 빈 구멍(파일)으로 내보내 순수 출력 비용만 쟀어요.
출력 줄 수 print 반복 write 반복 join 일괄 print/join
─────────────────────────────────────────────────────────────────
100,000 0.0113초 0.0063초 0.0043초 약 2.7배
1,000,000 0.1100초 0.0618초 0.0445초 약 2.5배
숫자를 보면 흥미로운 점이 두 가지입니다. 첫째, 빅오는 똑같은데 실측이 2~3배 차이가 납니다. 백만 줄을 내보내는 일은 어느 쪽이든 O(n)이에요. 바뀌는 건 A-2에서 입력에 대해 짚은 것과 똑같이 상수배입니다. "빅오는 상수를 버린다"고 했던 그 버려진 상수가, 시간 제한에 아슬아슬하게 걸린 문제에서 통과와 실패를 가르는 거죠.
둘째, 차이가 "10배"쯤 날 거라 짐작한 분도 있을 텐데, 실제론 2~3배입니다. 과장 없이 말하면 이 정도예요. 대신 이 2~3배가 결정적인 순간이 있습니다. 풀이가 시간 제한에 딱 걸쳐 있을 때요. 그럴 때 출력 방식 하나만 바꿔도 통과선을 넘길 수 있습니다. 중간 단계인 write 반복(sys.stdout.write를 줄마다)도 print보다는 빠르지만, 호출이 여전히 n번이라 일괄 출력에는 못 미칩니다.
print()를 n번 답을 모아 write 1번
┌───┐ ┌───┐ ┌───┐ ┌─────────────────┐
│ 1 │ │ 2 │ │ 3 │ ... │ "1\n2\n3\n..." │ → write 한 번
└───┘ └───┘ └───┘ └─────────────────┘
호출 n번 (상수 큼) 호출 1번 (상수 작음)
💡 한 줄 정리
입력만큼 출력도 병목이 된다. 답을 줄마다 print()하지 말고, 리스트에 모아 '\n'.join으로 한 번에 내보낸다. 빅오는 그대로지만 상수배(실측 2~3배)가 시간 제한 앞에서 승부를 가른다.
🙋 학생 질문 — "튜터님, 그럼 항상 모아서 출력해야 하나요?"
아니요, 항상은 아닙니다. 출력이 몇 줄뿐이면 print()를 그냥 쓰세요. 차이가 눈에 안 보일뿐더러, 코드가 더 읽기 쉽거든요. 모아서 출력하는 건 출력이 수만~수십만 줄로 많을 때 꺼내는 카드입니다.
그리고 맞바꿈이 있다는 걸 잊지 마세요. 답을 다 모으는 만큼 메모리를 씁니다(공간 O(전체 출력 길이)). 출력이 극단적으로 크고 메모리 제한이 빡빡한 드문 경우엔, 적당한 크기씩 나눠 내보내는 절충도 씁니다. 대부분의 코테에서는 모아서 한 번에가 안전한 기본값이에요.
Step 5: "재귀가 너무 깊으면 터진다" — 함정 ① 깊이와 시간 초과 (~20분)
지난 시간의 세 번째 약속, 자주 틀리는 함정으로 들어갑니다. 함정의 공통점은 이거예요. 논리는 완벽한데 특정 경계에서만 무너진다는 것. 평범한 입력에서는 멀쩡히 통과하다가 딱 한 지점에서 떨어지니, 눈으로 코드를 읽어서는 안 잡히고 경계 입력을 직접 넣어 봐야 잡힙니다.
첫 함정은 재귀 깊이입니다. E-1(재귀)과 C-1(트리), F-2(유니온 파인드)에서 여러 번 스쳐 지나간 그 문제요. 파이썬은 재귀 호출이 1,000단쯤 쌓이면 RecursionError로 멈춥니다. 그런데 재귀 깊이가 입력 크기 n을 그대로 따라가는 풀이가 의외로 많아요.
1부터 n까지 더하는 걸 재귀로 짜 봅시다. 논리는 나무랄 데 없습니다.
# patterns/pitfalls.py
def recursive_sum_to(n):
"""1부터 n까지 더한 값을 재귀로 구한다 — 깊이가 n이라 큰 n에서 터진다.
시간 O(n) · 공간 O(n) (호출 스택이 n단 쌓인다)
논리는 완벽하다. 문제는 n이 곧 재귀 깊이라는 것. 파이썬 기본 한도가 1000
언저리라 n이 수천만 돼도 RecursionError가 난다.
"""
if n == 0:
return 0
return n + recursive_sum_to(n - 1)
이걸 recursive_sum_to(10000)으로 부르면 어떻게 될까요? 답인 50,005,000이 나오는 게 아니라, RecursionError로 터집니다. 호출이 만 단까지 쌓이기 전에 파이썬이 손을 들어 버리거든요. 코테 입력이 n ≤ 100,000이면 이 풀이는 손도 못 대고 떨어집니다.
recursive_sum_to(4) ─ 호출이 깊이만큼 쌓인다
sum(4)
└ sum(3)
└ sum(2)
└ sum(1)
└ sum(0) ← n이 크면 이 깊이가 n까지 → 1000 넘으면 붕괴
해법은 두 갈래입니다. 하나는 한도를 올리는 것 — E-1에서 배운 sys.setrecursionlimit이죠. 다만 이건 대증요법이에요. 파이썬 호출 스택은 한 프레임이 무거워서, 깊이가 수십만이 되면 이번엔 메모리 쪽에서 터집니다. 그래서 한도를 올리더라도 안전하게 되돌리는 습관이 필요해요.
# patterns/pitfalls.py
@contextmanager
def recursion_limit(new_limit):
"""재귀 깊이 한도를 잠시 올렸다가 블록을 벗어나면 반드시 원래대로 되돌린다.
시간 O(1) · 공간 O(1)
한도를 올린 채 두면 그 뒤에 도는 다른 코드까지 영향을 받는다. 되돌리는 일을
finally에 맡겨야 중간에 예외가 나도 한도가 원상 복구된다.
"""
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(new_limit)
try:
yield
finally:
sys.setrecursionlimit(old_limit)
더 근본적인 해법은 반복문으로 바꾸는 것입니다. 재귀가 자동으로 쓰던 호출 스택을, 우리가 리스트로 직접 들고 있으면 깊이 한도와 무관해져요. C-1에서 트리 순회를 명시적 스택으로 바꿨던 것과 같은 손놀림입니다. 실제로 위 합은 반복문으로 짜면 공간도 O(n)에서 O(1)로 줄죠.
그럼 언제 한도를 올리고, 언제 반복으로 갈아탈까요? 짜기 전에 예상 깊이를 먼저 가늠하면 판단이 섭니다. 입력의 모양과 크기로 최대 깊이를 어림하고, 그에 따라 전략을 고르는 도구예요.
# patterns/pitfalls.py
def recursion_strategy(shape, n, limit=None):
"""예상 깊이를 보고 재귀를 그대로 둘지, 한도를 올릴지, 반복으로 바꿀지 고른다.
반환값
"as-is" 지금 한도 안에 여유 있게 든다. 재귀 그대로 간다
"raise-limit" 한도만 올리면 된다
"switch-to-iteration" 너무 깊다. 한도를 올려도 위험하니 반복문으로 바꾼다
"""
if limit is None:
limit = sys.getrecursionlimit()
depth = estimate_recursion_depth(shape, n)
if depth + RECURSION_MARGIN <= limit:
return "as-is"
if depth <= SAFE_RAISE_MAX:
return "raise-limit"
return "switch-to-iteration"
한 줄로 이어진 그래프("chain")를 넣어 보면 판단이 이렇게 갈립니다.
recursion_strategy("chain", 500) → "as-is" (깊이 500, 한도 안)
recursion_strategy("chain", 100000) → "raise-limit" (깊이 10만, 올리면 됨)
recursion_strategy("chain", 5000000) → "switch-to-iteration" (깊이 500만, 반복으로)
recursion_strategy("balanced_tree", 10000000) → "as-is" (균형 트리는 깊이 log)
마지막 줄이 핵심입니다. 같은 천만 개라도 균형 잡힌 트리는 깊이가 log이라 재귀 그대로 안전해요. 깊이 함정은 "n이 크다"가 아니라 "깊이가 n을 따라간다"가 문제입니다. 한 줄로 늘어진 그래프나 한쪽으로 치우친 트리가 위험하고, 균형 잡힌 구조는 괜찮죠.
💡 한 줄 정리
재귀 깊이가 입력 크기 n을 따라가면 RecursionError로 터진다. 짜기 전에 예상 깊이를 가늠해, 얕으면 그대로·조금 깊으면 한도를 올리고·너무 깊으면 반복문으로 갈아탄다.
🙋 학생 질문 — "튜터님, 그냥 항상 한도를 크게 올려 두면 안 되나요?"
그 유혹이 정말 큽니다. 코드 맨 위에 sys.setrecursionlimit(10**9) 한 줄 써 두면 편하잖아요. 그런데 그게 함정이에요.
재귀 한도는 "여기까지만 쌓아라"라고 파이썬이 그은 안전선입니다. 이 선을 무작정 높이면, 진짜로 그만큼 깊어졌을 때 이번엔 실제 스택 메모리가 바닥나 인터프리터가 통째로 죽어요(RecursionError가 아니라 더 험한 방식으로요). 한도를 올리는 건 "1,000보다 조금 더 깊은" 재귀를 위한 것이지, 수십만 깊이를 감당하려는 게 아닙니다. 깊이가 정말 크면 답은 하나예요. 반복문으로 바꾸는 것.
Step 6: "예제는 맞는데 채점에서 떨어진다" — 함정 ② 경계·예외 입력 (~20분)
두 번째 함정 묶음은 경계와 예외 입력입니다. 이게 정말 얄궂어요. 예제 입력은 다 맞아서 "됐다!" 하고 제출하는데, 채점기의 숨은 테스트 하나에서 무너지거든요. 왜냐하면 함정은 하나같이 평범한 입력에서는 정답을 내고, 극단적인 경계 하나에서만 틀리기 때문입니다.
자주 걸리는 다섯 가지를 모아 봤습니다. 각 함정마다 "그럴듯해 보이는 틀린 코드"가 어떤 입력에서 실제로 무너지는지, 직접 넣어 확인한 값입니다.
| 함정 | 틀리는 입력 | 틀린 코드의 결과 | 정답 |
|---|---|---|---|
| ① off-by-one (범위 끝) | binary_search([1,2,3], 5) |
IndexError |
-1 |
| ② 빈 입력·n=1 | max_gap([5]) |
ValueError |
0 |
| ③ 초깃값(전부 음수) | max_subarray([-2,-3,-1]) |
0 |
-1 |
| ④ 중복 값 | second_largest([5,5,3]) |
5 |
3 |
| ⑤ 음수 나눗셈 | -7 // 2 |
-4 |
-3 |
다섯 개 모두 실제로 실행해 오답을 확인한 것들이에요. 이 중 둘만 코드로 자세히 보고, 나머지는 표의 감각으로 챙겨 가세요.
먼저 ① off-by-one입니다. 이진 탐색에서 오른쪽 끝을 len(arr)로 잡는 실수예요. 배열 안에 있는 값을 찾을 땐 멀쩡히 맞습니다. 그런데 모든 원소보다 큰 값을 찾으면 인덱스가 배열 밖으로 밀려나 터져요.
# patterns/pitfalls.py
def binary_search_wrong(arr, target):
"""정렬된 배열에서 target의 인덱스를 찾는다 — 오른쪽 끝이 한 칸 넘쳐 있다.
시간 O(log n) · 공간 O(1)
right를 len(arr)로 잡았다(len(arr) - 1이어야 한다). 배열 안에 있는 값을
찾을 때는 멀쩡히 맞는데, target이 모든 원소보다 크면 left가 len(arr)까지
밀려 arr[len(arr)]를 읽고 IndexError가 난다. '없는 값을 찾는' 입력이
테스트 케이스에 하나라도 있으면 그때 터진다.
"""
left, right = 0, len(arr)
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
if arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
binary_search_wrong([1, 2, 3], 2)는 1을 잘 돌려줍니다. 그런데 binary_search_wrong([1, 2, 3], 5)처럼 없는 큰 값을 찾으면 arr[3]을 읽으려다 IndexError로 터져요.
고치는 건 딱 한 글자입니다. right를 len(arr) - 1로 잡으면, 없는 값이면 조용히 -1을 돌려주죠. "없는 값을 찾는" 테스트가 채점기에 하나라도 있으면 이 한 글자가 당락을 가릅니다.
다음은 ③ 초깃값 함정입니다. 연속 부분 배열의 최대 합(카데인 알고리즘)에서 최댓값의 초깃값을 0으로 잡는 실수예요.
# patterns/pitfalls.py
def max_subarray_sum_wrong(arr):
"""연속 부분 배열의 합 중 최댓값을 구한다 — 전부 음수면 0을 돌려준다.
시간 O(n) · 공간 O(1)
최댓값의 초깃값을 0으로 잡은 게 함정이다. 0은 '아무것도 고르지 않은 상태'의
합이라, 원소를 반드시 하나는 골라야 하는 문제에서는 답이 될 수 없다. 배열에
양수가 하나라도 있으면 멀쩡히 맞고, 전부 음수일 때만 조용히 0을 뱉는다.
"""
best = 0
cur = 0
for x in arr:
cur = max(x, cur + x)
best = max(best, cur)
return best
max_subarray_sum_wrong([1, -2, 3])은 3으로 잘 맞습니다. 양수가 하나라도 있으면 멀쩡하거든요. 그런데 [-2, -3, -1]처럼 전부 음수면, 정답은 그나마 가장 큰 -1인데 이 코드는 0을 뱉어요. 0은 "아무것도 안 고른 합"이라, 반드시 하나는 골라야 하는 문제에서는 나올 수 없는 답이죠. 초깃값을 첫 원소로 잡으면 전부 음수여도 정확히 -1이 나옵니다.
나머지 셋도 결이 같아요. ② 빈 입력은 원소가 하나뿐일 때 max()에 빈 것이 넘어가 터지고, ④ 중복은 최댓값이 두 번 나올 때 "두 번째로 큰 값" 대신 최댓값을 돌려주고, ⑤ 음수 나눗셈은 파이썬 //가 0 방향이 아니라 아래쪽으로 버려서 -7 // 2가 -3이 아닌 -4가 됩니다.
이 함정들을 피하는 습관은 하나예요. 풀이를 다 짠 뒤, 극단적인 입력을 스스로 넣어 보는 것. 원소가 0개면? 1개면? 전부 같으면? 전부 음수면? 답이 없으면? 이 다섯 질문을 제출 전에 던지는 것만으로 채점기의 숨은 테스트 절반을 미리 막습니다.
함정은 여기 숨는다 — "가장자리"
[정상 입력 구간] ← 예제는 여기. 잘 통과한다
┌──────────────────┐
0 1 n-1 n ← 함정은 양 끝(0개·1개·마지막 칸)에 숨는다
빈 입력 최댓값·마지막 인덱스
원소 1개 범위를 넘는 접근
💡 한 줄 정리
함정은 평범한 입력이 아니라 가장자리에 숨는다. 빈 입력·원소 1개·전부 같음·전부 음수·답 없음 — 이 다섯 경계를 제출 전에 스스로 넣어 보면 채점기의 숨은 테스트를 미리 막는다.
🙋 학생 질문 — "튜터님, 이 많은 경계를 매번 다 확인하긴 어렵지 않나요?"
전부 다 볼 필요는 없어요. 문제 유형마다 잘 걸리는 경계가 정해져 있거든요.
배열·문자열이면 빈 것과 원소 1개를 봅니다. 최댓값·최솟값을 구하는 문제면 "전부 음수" "전부 같음"을 의심하고요. 탐색이면 "찾는 게 없을 때"를, 나눗셈·나머지가 끼면 음수를 넣어 봅니다. 그래프면 정점 1개나 간선 0개를 확인하죠.
이걸 몸에 붙이는 가장 좋은 방법은, 틀려 본 경계를 자기만의 체크리스트로 모으는 거예요. 한 번 IndexError로 떨어져 보면 그 경계는 평생 안 잊습니다. 실수는 최고의 선생이거든요.
Step 7: "세 문제에 두 시간, 어떻게 나누나" — 시간 배분과 부분 점수 (~20분)
알고리즘을 다 알아도 마지막에 무너지는 이유가 하나 더 있습니다. 시간 관리예요. 실전 코딩테스트는 대개 문제 서너 개에 두세 시간이 주어집니다. 이 시간을 어떻게 쓰느냐가, 같은 실력에서도 합격과 불합격을 가릅니다.
가장 흔한 실패 패턴부터 봅시다. 첫 문제에 꽂혀서 한 시간을 통째로 쓰고, 정작 풀 수 있었던 뒤 문제를 손도 못 대고 시험이 끝나는 경우요. 한 문제에 매달리는 건 마음은 편한데 점수는 최악입니다. 시험은 "가장 어려운 문제를 푼 사람"이 아니라 "주어진 시간에 가장 많은 점수를 모은 사람"이 이기는 게임이거든요.
그래서 시간을 이렇게 씁니다. 시험이 시작되면 먼저 모든 문제를 5분 안에 훑어요. Step 1~3에서 익힌 대로, 각 문제의 입력 크기와 키워드를 읽어 난이도와 유형을 가늠합니다. 그리고 쉬워 보이는 것부터 손을 댑니다.
두 시간 · 세 문제 — 시간을 쓰는 순서
0:00 ─┬─ 전체 훑기 (5분): 크기·키워드로 난이도 파악
│
0:05 ─┼─ 쉬운 문제부터 확실히 (A → B → C 순)
│ 막히면 미련 없이 다음으로 넘어간다
│
1:40 ─┼─ 남은 시간: 못 푼 문제 + 부분 점수 노리기
│
2:00 ─┴─ 제출 마감
여기서 핵심 원칙 두 가지를 새기세요.
첫째, 한 문제에 시간 상한을 둡니다. "이 문제에 30분까지만 쓰겠다"고 정하고, 넘으면 미련 없이 넘어가요. 막힌 문제를 붙잡고 있을 시간에, 확실히 풀 수 있는 다른 문제를 푸는 게 점수에 훨씬 이득입니다. 넘어간 문제는 다른 걸 풀다가 아이디어가 떠오르면 그때 돌아오면 돼요. 신기하게도 잠시 손을 떼면 안 보이던 실마리가 보일 때가 많습니다.
둘째, 부분 점수를 노립니다. 많은 코테가 테스트 케이스별로 점수를 줘요. 완벽한 풀이를 못 짜겠으면, 일단 되는 풀이라도 내는 겁니다. Step 1에서 배운 걸 거꾸로 쓰는 거예요. n이 크면 O(n log n)이 필요하지만, 그 풀이가 안 떠오르면 작은 입력만 통과하는 O(n²) 완전탐색이라도 제출합니다. 만점은 아니어도 부분 점수는 챙기죠.
이게 왜 중요하냐면, 실전에서 만점 풀이가 안 떠오르는 순간이 반드시 오기 때문입니다. 그때 "완벽하지 않으면 안 낸다"는 태도는 0점이고, "되는 만큼이라도 낸다"는 태도는 부분 점수예요. 특히 맞는 느린 풀이를 먼저 확보해 두고 시간이 남으면 최적화하는 순서가 안전합니다. 빠른데 틀린 풀이보다, 느려도 맞는 풀이가 부분 점수라도 건지거든요.
지난 시간(F-2)에 살짝 흘렸던 이야기, 기억하시나요. "실전에서 시간을 잡아먹는 건 구현이 아니라 무엇을 꺼낼지 정하는 3분"이라고요. 그 3분을 Step 1~3의 분별로 줄였다면, 이번 Step은 나머지 시간을 어디에 쓸지를 정리한 셈입니다. 분별로 시작을 빠르게, 시간 배분으로 끝을 안전하게.
💡 한 줄 정리
시험은 가장 어려운 문제를 푼 사람이 아니라 시간 안에 점수를 가장 많이 모은 사람이 이긴다. 전체를 훑고 쉬운 것부터, 한 문제엔 시간 상한을 두고, 만점이 안 되면 부분 점수라도 챙긴다.
🙋 학생 질문 — "튜터님, 쉬운 문제부터 풀면 어려운 문제 풀 시간이 부족하지 않나요?"
거꾸로예요. 쉬운 문제부터 푸는 게 어려운 문제에 쓸 시간을 벌어 줍니다.
이유는 두 가지입니다. 첫째, 쉬운 문제를 빨리 확실히 끝내면 마음이 편해져요. 한 문제라도 맞혀 뒀다는 안정감이 남은 문제에 집중하게 해 줍니다. 반대로 첫 문제부터 어려운 걸 붙잡으면, 시간은 흐르고 푼 건 없어서 조급해지죠. 그 조급함이 실수를 부릅니다.
둘째, 시간 대비 점수 효율이 달라요. 쉬운 문제 30분에 100점, 어려운 문제 90분에 100점이라면, 쉬운 것부터 푸는 게 같은 시간에 더 많은 점수를 모읍니다. 어려운 문제는 애초에 시간이 많이 드니, 확실한 점수를 먼저 쓸어 담고 남은 시간을 거기 붓는 게 맞아요.
Step 8: "시험장에 앉기 전에" — 당일 체크리스트 (~20분)
마지막 Step은 실전 그 자체입니다. 아무리 실력이 있어도 준비가 어설프면 시험장에서 당황해요. 오늘 배운 걸 실제 코딩테스트에서 꺼내 쓰기 위한 체크리스트를 정리하겠습니다.
먼저 환경입니다. 국내 채용은 대개 프로그래머스에서, 글로벌·대기업은 LeetCode에서 코딩테스트를 봅니다. 알고리즘 연습은 백준·프로그래머스에서 문제 유형을 쌓는 게 정석이고요. 플랫폼마다 화면은 조금씩 달라도, 입력을 읽어 답을 출력하는 뼈대는 같습니다.
언어는 파이썬을 권합니다. 이 과목 내내 그랬듯, 문법이 간결해 알고리즘 자체에 집중할 수 있거든요.
다만 파이썬은 다른 언어보다 느린 편이라, 시간 제한에 걸쳐 있으면 두 가지를 챙기세요. Step 4의 입출력 최적화(입력은 sys.stdin, 출력은 모아서 한 번에), 그리고 Step 5의 재귀 한도(sys.setrecursionlimit이나 반복 전환). 이 두 가지가 파이썬으로 코테를 볼 때 가장 자주 발목을 잡는 지점입니다.
이제 문제를 푸는 순서를 오늘 배운 것으로 엮어 봅시다. 시험장에서 문제 하나를 받으면 이 순서로 움직이세요.
문제 하나를 받으면 — 오늘 배운 순서대로
1. 입력 크기 n 을 본다 → 허용 복잡도를 정한다 (Step 1)
2. 키워드로 알고리즘을 좁힌다 → 후보를 모은다 (Step 2·3)
3. 크기로 후보를 거른다 → 하나를 고른다 (Step 1+3)
4. 짠다 → 맞는 풀이를 먼저 확보 (Step 7)
5. 경계를 스스로 넣어 본다 → 빈 입력·1개·전부 음수 (Step 6)
6. 입출력을 점검한다 → 큰 입력이면 최적화 (Step 4·5)
7. 제출한다 → 부분 점수라도 챙긴다 (Step 7)
그리고 제출 버튼을 누르기 전에 마지막으로 던질 질문들입니다. 이 체크리스트 하나가 어이없는 실수로 떨어지는 걸 막아 줘요.
제출 전 최종 점검
[ ] 입력 크기로 내 풀이의 빅오가 시간 안에 드는가?
[ ] 빈 입력 · 원소 1개 · 전부 같음 · 전부 음수를 넣어 봤는가?
[ ] 답이 없을 때(경로 없음·해 없음)를 처리했는가?
[ ] 출력 형식이 정확한가? (공백·줄바꿈·소수점 자릿수)
[ ] 재귀가 깊어질 수 있으면 한도를 올리거나 반복으로 바꿨는가?
[ ] 큰 입력이면 sys.stdin / 모아서 출력을 썼는가?
이 질문들이 낯설지 않죠. 전부 오늘 여덟 걸음에서 하나씩 다룬 것들입니다. 시험장에서 이 목록을 머릿속으로 훑을 수 있다면, 여러분은 이미 "짜고 나서 되나 안 되나 조마조마하게 기다리는" 단계를 넘어선 거예요.
💡 한 줄 정리
문제를 받으면 크기→키워드→거르기→구현→경계 점검→입출력→제출의 순서로 움직이고, 제출 전 체크리스트로 어이없는 실수를 막는다. 오늘 배운 여덟 걸음이 그대로 실전의 순서다.
🙋 학생 질문 — "튜터님, PyPy가 파이썬보다 빠르다던데 그걸 써야 하나요?"
들어 두면 좋은 이야기예요. PyPy는 파이썬 코드를 더 빠르게 돌려 주는 다른 실행기라, 같은 코드가 몇 배 빨라지기도 합니다. 그래서 파이썬 풀이가 시간 초과에 아슬아슬할 때 PyPy로 제출하면 통과하는 경우가 있어요.
다만 이건 플랫폼이 PyPy를 지원할 때만 쓸 수 있는 카드입니다. 지원 여부는 시험마다 다르니, 있으면 활용하되 없다고 당황할 필요는 없어요.
그리고 순서가 중요합니다. PyPy는 "느린 실행기를 빠른 실행기로 바꾸는" 최후의 수단이지, 느린 알고리즘을 감춰 주는 마법이 아니에요. O(n²)를 PyPy로 돌린다고 O(n log n)이 되진 않습니다. 먼저 알고리즘과 빅오를 맞추고, 그래도 상수배가 아쉬울 때 마지막에 꺼내는 도구로 기억하세요.
마무리
오늘은 스무 개의 무기를 실전에서 꺼내 드는 법을 다뤘습니다. 지문의 입력 크기를 보고 허용 복잡도를 정하고, 키워드로 알고리즘을 좁히고, 입출력과 경계 함정을 피하고, 시간을 배분해 점수를 모으는 것. 새 알고리즘을 배운 게 아니라, 이미 아는 것을 시험장에서 쓰는 법을 배운 하루였어요.
오늘 배운 핵심 세 가지
💡 하나 — 아는 것과 시간 안에 꺼내 쓰는 것은 다른 능력이다. 문제를 받으면 풀이보다 입력 크기 n을 먼저 봅니다. "1초 1억" 표를 거꾸로 읽어 허용 복잡도를 정하고, 지문의 키워드로 알고리즘 후보를 좁히죠. "사이클"이면 유니온 파인드, "순서"면 위상 정렬, "전부 잇기"면 최소 신장 트리. 키워드로 후보를 모으고 크기로 후보를 거르면, "무엇을 꺼낼지 정하는 3분"이 30초로 줄어듭니다.
💡 둘 — 입출력과 경계 함정은 알고리즘만큼 중요하다. 맞는 풀이도 떨어질 수 있어요. 출력을 줄마다 print()하면 상수배 때문에 시간 초과가 나고(모아서 한 번에), 재귀 깊이가 n을 따라가면 터지고(반복으로 전환), 빈 입력·전부 음수 같은 경계 하나에서 조용히 오답을 냅니다. 빅오가 같아도 상수배가, 논리가 맞아도 경계가 당락을 가릅니다. 제출 전 극단적인 입력을 스스로 넣어 보는 습관이 이걸 막아요.
💡 셋 — 시험은 점수를 모으는 게임이다. 가장 어려운 문제를 푼 사람이 아니라, 주어진 시간에 점수를 가장 많이 모은 사람이 이깁니다. 전체를 훑고 쉬운 것부터, 한 문제엔 시간 상한을 두고, 만점이 안 되면 부분 점수라도 챙깁니다. 맞는 느린 풀이를 먼저 확보하고 시간이 남으면 최적화하는 순서가 안전해요.
과목을 마치며
여기까지 오느라 정말 고생 많으셨습니다. 이 한 강만이 아니라, 스무 개 모듈 전부를요.
기억하시나요. 이 과목의 첫 시간, A-1에서 저는 여러분께 화면 하나를 보여 드렸습니다. "시간 초과(Time Limit Exceeded)". 정답은 맞는데 너무 느려서 떨어지는 그 화면 앞에서 물었죠. "분명히 돌아가는 코드인데 왜 떨어질까요?"
그때 우리에겐 그 화면을 읽을 눈이 없었습니다. 그래서 빅오로 코드의 속도를 재는 법(A)부터 시작해, 데이터를 담는 그릇(B·C)을 선형에서 비선형으로 익히고, 그 위에서 정렬과 탐색(D)으로 데이터를 다루고, 문제를 푸는 다섯 사고법(E)을 장착하고, 고급 알고리즘과 실전(F)으로 걸어왔습니다. 그 모든 모듈의 바닥에는 언제나 A-1의 빅오가 깔려, "이게 시간 안에 들어오나"를 매번 되묻게 했죠.
이제 그 "시간 초과" 화면을 다시 봅시다. 달라진 게 있죠. 여러분은 이제 그 화면을 미리 읽어 냅니다. 입력 크기를 보고 어떤 복잡도가 필요한지 알고, 지문을 읽고 어떤 알고리즘을 꺼낼지 알고, 어디서 시간 초과가 날지 짜기 전에 압니다. A-1에서 제가 약속했던 것 — "껍데기만 아는 코더에서 벗어나 이 풀이가 통과할지 미리 아는 엔지니어로 도약"하는 것 — 을 여러분은 스무 걸음에 걸쳐 실제로 해냈습니다.
물론 여기가 끝은 아니에요. 알고리즘은 손에 익어야 진짜 내 것이 됩니다. 이제부터는 배운 무기를 들고 실전 문제를 꾸준히 푸는 것이 남았어요. 프로그래머스 Lv2~3, LeetCode Medium을 하루 한두 문제씩 풀다 보면, 오늘 세운 분별표와 체크리스트가 머릿속에서 자동으로 돌기 시작합니다. 그때가 되면 코딩테스트는 더 이상 넘어야 할 벽이 아니라, 여러분의 사고력을 보여 주는 무대가 될 거예요.
스무 개 모듈을 완주한 여러분은, 이미 그 무대에 오를 준비가 되었습니다. 지금까지 함께 걸어 주셔서 고맙습니다. 홍순구 튜터였습니다. 여러분의 합격을 진심으로 응원합니다.
과제
오늘은 새 알고리즘 대신 실전 감각을 다뤘으니, 과제도 그 감각을 손에 익히는 문제들입니다. 코드를 짜기 전에 "이게 통과할까"를 먼저 판단하고, 경계를 스스로 넣어 보는 습관을 길러 보세요.
[기초] 이 풀이, 통과할까?
문제의 입력 제한 n과 후보 풀이의 시간 복잡도가 주어질 때, 그 풀이가 1초 안에 통과할지 판정하는 함수 will_pass(n, complexity)를 작성하세요. complexity는 "n", "n log n", "n^2", "2^n" 중 하나의 문자열입니다. "1초 ≈ 1억 연산" 잣대를 씁니다.
예를 들어 will_pass(100000, "n^2")는 10만의 제곱이 100억이라 1초를 넘으니 False, will_pass(100000, "n log n")은 약 170만 연산이라 True입니다.
작성한 뒤, 다음 세 가지에 직접 답해 보세요. n = 1000일 때 네 복잡도(n, n log n, n^2, 2^n) 중 통과하는 것은 각각 무엇인가요? n = 1,000,000이면요? 그리고 2^n이 통과하는 n의 상한은 대략 몇인가요?
예상 시간 20분 · 난이도 하 · 힌트 — n log n의 연산 횟수는 n * log2(n)으로 어림하면 됩니다. 2^n은 n이 조금만 커도 폭발하니, 값을 직접 계산해 1억과 비교하세요.
[응용] 빈틈을 찾아라
아래는 "리스트에서 가장 큰 값과 두 번째로 큰 값의 차이를 구하는" 함수입니다. 얼핏 맞아 보이지만, 어떤 입력에서 틀리거나 터집니다.
def top_gap(arr):
biggest = max(arr)
arr.remove(biggest)
second = max(arr)
return biggest - second
이 함수가 무너지는 입력을 최소 두 종류 찾아, 각각 어떤 값이 나오거나 어떤 예외가 터지는지 실제로 실행해 확인하세요. 그리고 모든 경계를 견디는 top_gap_fixed(arr)로 고쳐 쓰세요. 원소가 1개 이하일 때, 최댓값이 중복일 때 각각 어떻게 처리할지 스스로 정하고 그 이유를 한 문단으로 적으세요.
예상 시간 30분 · 난이도 중 · 힌트 — 오늘 Step 6에서 본 함정 중 두 가지가 이 짧은 함수에 동시에 숨어 있습니다. 하나는 빈 입력·원소 1개, 다른 하나는 중복이에요. arr.remove가 원본을 바꾼다는 점도 함정의 씨앗입니다.
[심화] 백만 줄을 처리하라
정수 n개가 주어집니다(1 ≤ n ≤ 1,000,000). 각 정수를 제곱한 값을 한 줄에 하나씩 출력하되, n이 백만까지 커도 시간 안에 통과하는 함수 solve_squares(read_all, write)를 작성하세요. read_all은 입력 전체를 문자열로 돌려주는 함수(실전에선 sys.stdin.read), write는 출력 함수(실전에선 sys.stdout.write)입니다.
첫 줄에 n, 그다음 n개의 정수가 (여러 줄에 걸쳐 있을 수 있음) 주어진다고 하고, 입력은 토막 단위로 한 번에 읽고 출력은 모아서 한 번에 내보내세요. 왜 이 문제에서 input()과 줄마다 print()가 위험한지, 여러분의 풀이가 그걸 어떻게 피하는지 시간·공간 복잡도와 함께 적으세요.
예상 시간 35분 · 난이도 상 · 힌트 — 읽기는 Step 4의 토막 일괄 읽기, 출력은 '\n'.join을 떠올리세요. 입력이 몇 줄로 나뉘어 있든 read_all().split()으로 한 번에 쪼개면 줄 구조를 신경 쓸 필요가 없습니다. 테스트할 때는 io.StringIO로 가짜 입력과 출력을 주면 됩니다.
생각해볼 주제
1. 빠른데 틀린 풀이와 느려도 맞는 풀이, 무엇을 먼저 낼까
오늘 우리는 "맞는 느린 풀이를 먼저 확보하라"고 했습니다. 그런데 실무 개발은 정반대일 때가 많아요. 서비스 코드는 느리면 사용자가 떠나니까요. 코딩테스트와 실무에서 "정확성"과 "속도"의 우선순위가 왜 다른지, 그리고 그 차이가 각각 무엇을 측정하려는 것인지 정리해 보세요. 부분 점수라는 채점 방식이 이 우선순위에 어떤 영향을 주는지도 함께 생각해 보시면 좋습니다.
2. 키워드로 알고리즘을 고르는 도구는 사람을 대신할 수 있을까
Step 2에서 지문의 키워드로 알고리즘 후보를 추리는 도구를 만들었습니다. 하지만 그 도구는 후보를 확정하지 않고 여럿 돌려줬죠. 만약 이 도구를 "키워드만 넣으면 정답 알고리즘 하나를 딱 찍어 주는" 형태로 발전시킨다면, 어떤 상황에서 그 도구가 사람을 잘못된 길로 이끌 수 있을까요. 자동 분별기가 놓치는 것, 그래서 끝까지 사람의 판단으로 남겨 둬야 하는 부분이 무엇인지 생각해 보세요.
3. 코딩테스트 점수는 개발 실력과 얼마나 같을까
이 과목은 코딩테스트라는 관문을 통과하는 힘을 길렀습니다. 그런데 코테를 잘 보는 것과 좋은 개발자인 것이 완전히 같지는 않아요. 코딩테스트가 잘 측정하는 능력과, 코딩테스트로는 도무지 측정할 수 없는 개발자의 능력이 각각 무엇인지 나눠 보세요. 기업이 채용에서 여전히 코딩테스트를 보는 이유를, 그 한계를 알면서도 유지하는 관점에서 짚어 보시면 흥미로울 겁니다.
✅ 예시 답안정답 보기
과제와 생각해볼 주제의 예시답안입니다. 코드는 먼저 떠올려 보고, 막힌 뒤에 펼쳐 보세요. 정답을 외우는 게 아니라, "왜 이렇게 푸는가"를 손에 익히는 게 목표입니다.
🎯 [과제 1 예시답안] 이 풀이, 통과할까?
채점 포인트
| 항목 | 배점 | 확인 |
|---|---|---|
| "1초 ≈ 1억 연산" 잣대로 판정한다 | 30% | 연산 횟수를 1억과 비교 |
| 네 복잡도의 연산량을 정확히 어림한다 | 30% | n log n을 n * log2(n)로 |
2^n이 작은 n에서도 폭발함을 안다 |
20% | 값을 직접 계산해 비교 |
| 세 가지 질문에 정확히 답한다 | 20% | 아래 답 참고 |
풀이 예시
복잡도 이름마다 연산 횟수를 어림해 1억과 견주면 됩니다.
# algorithms/exercises_f3.py
def will_pass(n, complexity):
"""입력 크기 n에서 주어진 복잡도가 1초(약 1억 연산) 안에 드는지 판정한다.
complexity는 "n", "n log n", "n^2", "2^n" 중 하나. "1초 ≈ 1억" 잣대를 쓴다.
시간 O(1) · 공간 O(1) (2^n은 큰 정수 연산이지만 판정 규모는 상수로 본다).
"""
limit = 10 ** 8
if complexity == "n":
ops = n
elif complexity == "n log n":
ops = n * max(1, int(log2(n)))
elif complexity == "n^2":
ops = n * n
elif complexity == "2^n":
ops = 2 ** n
else:
raise ValueError(f"모르는 복잡도: {complexity}")
return ops <= limit
세 질문의 답은 이렇습니다.
n = 1000일 때는 n(1천), n log n(약 1만), n^2(100만)이 모두 1억 안에 들어 통과합니다. 2^n은 2의 1000제곱이라 상상도 못 할 크기라 탈락이에요. 즉 n, n log n, n^2 세 가지가 통과합니다.
n = 1,000,000이면 n(100만)과 n log n(약 2천만)만 통과하고, n^2은 10의 12승이라 탈락입니다. n, n log n 두 가지만 남죠.
2^n이 통과하는 상한은 약 26입니다. 2의 26제곱이 약 6,700만이라 아슬아슬하게 1억 안이고, 27제곱은 약 1억 3천만이라 넘거든요. 그래서 "완전탐색으로 부분집합을 다 뒤지는 풀이"는 원소가 스무 개 남짓일 때까지만 안전하다는 감각이 여기서 나옵니다.
💡 튜터의 한마디: 이 함수 자체는 O(1)로 순식간에 끝나지만, 진짜 값어치는 시험장에서 풀이를 짜기 전에 이 판단을 머릿속으로 하는 데 있어요. 지문에서 n ≤ 100,000을 보는 순간 "O(n²)는 안 되겠구나, O(n log n) 이하로 가야지"가 반사적으로 나와야 합니다. 이게 A-1부터 스무 개 모듈 내내 깔아 온 "1초 1억" 척추의 완성형이에요.
🎯 [과제 2 예시답안] 빈틈을 찾아라
채점 포인트
| 항목 | 배점 | 확인 |
|---|---|---|
| 무너지는 입력을 두 종류 이상 찾는다 | 30% | 원소 1개·중복 최댓값 |
| 실제로 실행해 오답·예외를 확인한다 | 25% | 그럴듯한 추측이 아니라 실행 |
| 원본을 바꾸는 부작용까지 짚는다 | 20% | arr.remove의 함정 |
| 고친 판이 모든 경계를 견딘다 | 25% | 빈 입력·1개·중복 처리 |
풀이 예시
원래의 top_gap은 세 군데에서 무너집니다.
첫째, 원소가 1개 이하면 터집니다. top_gap([5])는 max로 5를 찾고 그걸 지운 뒤 빈 리스트에서 다시 max를 부르는데, 빈 것에 max를 쓰면 ValueError가 나요. 빈 입력 top_gap([])도 첫 max에서 바로 터집니다.
둘째, 최댓값이 중복이면 답이 틀립니다. top_gap([5, 5, 3])은 5 하나를 지워도 여전히 5가 남아 차이가 0으로 나와요. "두 번째로 큰 서로 다른 값"과의 차이인 2가 정답인데 말이죠.
셋째, arr.remove(biggest)가 원본 리스트를 바꿔 버립니다. 함수를 부른 쪽의 데이터가 조용히 훼손되는, 실무에서 특히 골치 아픈 부작용이에요.
set으로 중복을 없애고 크기를 먼저 확인하면 셋을 한꺼번에 막습니다.
# algorithms/exercises_f3.py
def top_gap_fixed(arr):
"""가장 큰 값과 두 번째로 큰 '서로 다른' 값의 차이. 없으면 0.
빈 입력·원소 1개·최댓값 중복을 모두 견딘다. 원본 리스트를 건드리지 않는다.
시간 O(n log n) · 공간 O(n) (중복 제거 + 정렬).
"""
uniq = sorted(set(arr))
if len(uniq) < 2:
return 0
return uniq[-1] - uniq[-2]
set은 원본을 복제해 새로 만들므로 입력을 건드리지 않고, 서로 다른 값이 2개 미만이면 두 번째로 큰 값이 없으니 0을 돌려줍니다. 정렬이 들어가 시간은 O(n log n), 공간은 O(n)이에요.
💡 튜터의 한마디: 이 과제의 핵심은 코드를 고치는 것보다 "어디서 무너지는지 실제로 넣어 확인하는" 습관입니다. 눈으로 읽으면 멀쩡해 보이는 함수가 [5]나 [5, 5, 3]에서 실제로 터지고 틀리는 걸 손으로 확인해 봐야, 제출 전에 스스로 경계를 넣어 보는 감각이 몸에 붙어요. 반례는 그럴듯하게 짐작하는 게 아니라 돌려서 확인하는 겁니다.
🎯 [과제 3 예시답안] 백만 줄을 처리하라
채점 포인트
| 항목 | 배점 | 확인 |
|---|---|---|
| 입력을 토막 단위로 한 번에 읽는다 | 30% | read_all().split() |
| 출력을 모아서 한 번에 내보낸다 | 30% | '\n'.join |
| 빅오는 그대로, 상수배가 바뀜을 설명한다 | 20% | O(n) 유지 |
input()·줄마다 print()의 위험을 짚는다 |
20% | 상수배 누적 |
풀이 예시
입력은 토막 일괄 읽기로, 출력은 한 문자열로 모아 한 번에 내보냅니다.
# algorithms/exercises_f3.py
def solve_squares(read_all, write):
"""첫 토막 n, 이어지는 n개 정수를 읽어 각 제곱을 한 줄에 하나씩 출력한다.
시간 O(n) · 공간 O(n). 실전: solve_squares(sys.stdin.read, sys.stdout.write).
"""
tokens = iter(read_all().split())
n = int(next(tokens))
squares = (str(int(next(tokens)) ** 2) for _ in range(n))
write("\n".join(squares) + "\n")
read_all().split()은 입력 전체를 한 번에 읽어 공백과 줄바꿈을 가리지 않고 토막으로 쪼갭니다. 그래서 값이 한 줄에 몰려 있든 여러 줄에 흩어져 있든 신경 쓸 필요가 없어요. 출력도 제곱한 값을 하나하나 print하지 않고, '\n'.join으로 한 문자열을 만들어 write를 딱 한 번 부릅니다.
왜 이렇게 하냐면, n이 백만이면 input()을 백만 번, print()를 백만 번 부르게 되는데 그 호출 하나하나의 상수 부담이 쌓여 시간 초과를 부르기 때문입니다. 빅오는 어느 쪽이든 O(n)으로 같아요. 바뀌는 건 상수배죠. 이 풀이는 읽기와 쓰기를 각각 한 번의 큰 작업으로 묶어 그 상수배를 줄입니다. 대신 입력과 출력을 통째로 메모리에 들고 있으므로 공간이 O(n)입니다. 시간을 사고 공간을 내주는 맞바꿈이에요.
💡 튜터의 한마디: 알고리즘이 O(n)으로 완벽해도 입출력에서 시간 초과가 나면 똑같이 떨어집니다. 특히 파이썬은 이 상수배에 민감해서, "풀이는 맞는데 왜 떨어지지?"의 범인이 입출력일 때가 많아요. 대량 입출력 문제를 만나면 반사적으로 이 골격 — 토막 일괄 읽기 + 모아서 출력 — 을 꺼내는 습관을 들이세요.
🤔 [생각해볼 주제 1] 빠른데 틀린 풀이와 느려도 맞는 풀이, 무엇을 먼저 낼까
문제 상황 요약
코딩테스트에서는 "맞는 느린 풀이를 먼저 확보하라"고 배웁니다. 그런데 실무 서비스 코드는 느리면 사용자가 떠나니, 속도가 곧 품질이죠. 같은 "정확성 대 속도"의 저울이 두 세계에서 반대로 기웁니다. 왜일까요.
튜터의 가이드 및 해설
두 세계가 측정하려는 게 다르기 때문입니다.
코딩테스트는 제한된 시간 안에 문제를 얼마나 정확히 해결하는가를 봅니다. 채점기는 부분 점수를 주니, 만점 풀이가 안 떠오를 때 "되는 만큼이라도" 내는 게 0점보다 낫죠. 그래서 "맞지만 느린 풀이"가 부분 점수라는 실체적 가치를 가집니다. 게다가 시험은 끝나면 그 코드를 다시 쓸 일이 없어요. 그 순간의 점수가 전부입니다.
실무는 다릅니다. 서비스 코드는 한 번 배포되면 수많은 사용자가 반복해서 실행합니다. 느린 코드는 매 요청마다 사용자를 기다리게 하고, 서버 비용을 늘리고, 결국 서비스를 떠나게 만들어요. 여기서 "맞지만 느린 코드"는 부분 점수가 아니라 지속적인 손실입니다. 그래서 실무는 정확성을 전제로 깔고 그 위에서 속도를 끝까지 추구하죠.
정리하면, 코딩테스트의 "정확성 먼저"는 일회성 채점 때문이고, 실무의 "속도도 필수"는 반복 실행 때문입니다. 부분 점수라는 채점 방식이 코딩테스트에서 "일단 맞히고 보자"를 합리적인 전략으로 만드는 거예요.
🎯 코테·면접에선 이렇게 나온다
면접에서 "시간 복잡도를 개선한 경험"을 물으면, 두 세계의 차이를 아는 사람이 돋보입니다. "코딩테스트에서는 맞는 풀이를 먼저 확보하고 시간이 남으면 최적화했지만, 실무에서는 정확성을 전제로 처음부터 병목을 측정해 개선했다"처럼, 맥락에 따라 우선순위가 달라진다는 걸 짚으면 판단력이 있어 보여요.
💡 실무에선
"조급한 최적화는 만악의 근원"이라는 말이 있습니다. 실무에서도 처음부터 모든 걸 최적화하지는 않아요. 먼저 정확히 동작하게 만들고, 프로파일링으로 진짜 병목을 측정한 뒤 거기만 개선합니다. 코테의 "맞는 풀이 먼저"와 실무의 "측정 후 최적화"는 사실 "추측하지 말고 확인하라"는 같은 원칙의 두 얼굴이에요.
🤔 [생각해볼 주제 2] 키워드로 알고리즘을 고르는 도구는 사람을 대신할 수 있을까
문제 상황 요약
Step 2에서 만든 분별 도구는 후보를 여럿 돌려줬습니다. 만약 이걸 "키워드만 넣으면 정답 하나를 딱 찍어 주는" 형태로 발전시킨다면, 어떤 상황에서 사람을 잘못된 길로 이끌까요.
튜터의 가이드 및 해설
하나로 확정하는 도구의 위험은 틀렸을 때 다른 길을 막아 버린다는 데 있습니다.
실전 지문은 같은 키워드가 전혀 다른 알고리즘을 가리킬 때가 많아요. "최단"이라는 말이 가중치가 없으면 BFS, 있으면 다익스트라, 음수가 끼면 벨만-포드로 갈립니다. 키워드만 보고 하나를 찍는 도구는 이 갈림을 놓쳐요. 또 출제자가 일부러 익숙한 키워드를 함정으로 심어 두는 경우도 있습니다. "정렬"이라는 말이 보이지만 실제로는 정렬이 필요 없는 문제처럼요.
무엇보다 도구는 입력 크기와 제약 조건을 종합하는 판단을 못 합니다. "모든 경우"라는 말에 완전탐색이 걸려도, n이 크면 그건 오답이고 DP로 가야 하죠. 이 "키워드 + 크기 + 제약"을 함께 저울질하는 건 사람의 몫입니다. 그래서 좋은 도구는 답을 확정하지 않고 후보를 넓혀 주는 데서 멈춰요. 최종 선택은 사람이 하도록 남겨 두는 거죠.
🎯 코테·면접에선 이렇게 나온다
문제를 받고 무작정 코드부터 짜는 사람과, "이 지문의 키워드는 이걸 가리키지만 입력 크기를 보니 저 알고리즘이어야 한다"고 후보를 좁혀 가는 사람은 면접에서 확연히 갈립니다. 후자는 판단 과정을 말로 설명할 수 있어서 설령 최종 답이 막혀도 사고력을 보여 줄 수 있어요.
💡 실무에선
이 이야기는 요즘 코드를 대신 짜 주는 도구들에도 그대로 적용됩니다. 도구가 후보를 빠르게 제시해 줘도, 그게 이 맥락에 맞는지 판단하고 책임지는 건 결국 개발자예요. 도구를 후보를 넓히는 데 쓰되 최종 판단은 사람이 쥐는 태도가, 도구가 강해질수록 오히려 더 중요해집니다.
🤔 [생각해볼 주제 3] 코딩테스트 점수는 개발 실력과 얼마나 같을까
문제 상황 요약
이 과목은 코딩테스트라는 관문을 통과하는 힘을 길렀습니다. 그런데 코테를 잘 보는 것과 좋은 개발자인 것이 완전히 같지는 않아요. 코딩테스트가 잘 측정하는 것과 도무지 측정 못 하는 것은 각각 무엇일까요.
튜터의 가이드 및 해설
코딩테스트가 잘 재는 건 혼자서, 짧은 시간에, 명확히 정의된 문제를 정확히 푸는 능력입니다. 알고리즘 지식, 자료구조 선택, 복잡도 감각, 경계 처리의 꼼꼼함 같은 것들이죠. 이건 개발자의 중요한 기본기라, 코테 점수가 이 부분을 어느 정도 걸러 주는 건 사실입니다.
하지만 실무의 상당 부분은 코테가 못 재는 곳에 있어요. 요구사항이 애매한 문제를 정의부터 하는 능력, 남이 짠 코드를 읽고 고치는 능력, 몇 달 뒤의 내가 읽어도 이해되게 쓰는 가독성, 팀과 소통하고 협업하는 능력, 그리고 정답이 없는 설계에서 트레이드오프를 저울질하는 판단력. 이런 건 "정해진 입력에 정해진 출력"이라는 코테의 틀로는 담기지 않습니다.
그럼에도 기업이 코테를 유지하는 건, 완벽해서가 아니라 많은 지원자를 공정하고 효율적으로 거르는 1차 필터로 쓸 만하기 때문이에요. 코테는 "이 사람이 최소한의 문제 해결 기본기가 있는가"를 걸러 줄 뿐, "좋은 개발자인가"의 답까지 주지는 않습니다. 그 나머지는 면접과 실무에서 봅니다.
🎯 코테·면접에선 이렇게 나온다
이 관점을 아는 지원자는 코테를 통과한 뒤 면접에서 태도가 다릅니다. "코테는 기본기를 보여 주는 자리였고, 실무에서 중요한 협업과 설계는 이렇게 준비했다"처럼 코테의 한계를 스스로 알고 그 너머를 채운 사람은 성숙해 보여요. 코테 만점이 곧 실력이라 믿는 사람보다 훨씬요.
💡 실무에선
코딩테스트를 통과한 뒤가 진짜 시작입니다. 알고리즘 기본기는 밑바탕일 뿐, 실무에서는 읽기 좋은 코드, 함께 일하는 법, 애매한 문제를 정의하는 힘이 더 오래 여러분을 지탱해요. 그래서 이 과목을 마쳤다고 공부가 끝난 게 아니라, 좋은 개발자로 가는 여러 갈래 중 하나를 든든히 다진 거라고 생각하시면 됩니다.