| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- Tesselation
- Stack
- 이중우선순위큐
- std::vector
- 취준
- Output Merge
- 데이터분석
- 스택
- 어레이
- SetPass Call
- 디퍼드 렌더링
- rasterization
- 포워드 렌더링
- Fetching
- 힙
- occlusion culling
- batches
- Mipmap
- priority_queue
- bvh
- 코딩테스트
- graphics rendering pipeline
- UnReal
- DrawCall
- Input Assembler
- 그래픽스 파이프라인
- vertex shader
- 개발자면접
- c++
- im뱅크
- Today
- Total
평생 배우는 개발자
2025 NC soft 신입공채 게임 프로그래머 채용 코딩테스트 후기 본문
20251025
엔씨소프트 코딩테스트가 있었다. 프로그래머스 사이트로 시험이 진행되었고 총 3문제 120분이 주어졌다. 문제의 난이도는 크게 어렵지 않았다. 1,2번은 간단한 구현 문제여서 문제만 잘 읽고 그대로 표현하면 되는 문제였다.
진짜 문제는 3번이었다.
주어진 벡터의 원소들이 각각 무한히 뻗어가는 배수열을 가질 때,
그 전체 수열에서 k번째로 작은 수를 구하는 문제와 비슷하였다.
처음 문제를 읽었을 때는 “이건 간단하겠는데?” 싶었다.
배수들을 전부 나열해서 정렬한 다음, k번째 원소를 찾으면 되겠다고 생각했다.
하지만 곧바로 그 방식은 TLE(Time Limit Exceeded) 이 날 수밖에 없다는 걸 깨달았다.
왜냐하면 각 원소의 배수열이 무한히 이어지기 때문에
모든 수를 직접 생성하고 정렬하는 건 불가능한 접근이었기 때문이다.
그때부터 본격적으로 “이걸 어떻게 유한하게 줄일 수 있을까?”라는 고민이 시작됐다.
첫 시도: 이분 탐색
평소처럼 “k번째 원소를 찾는 문제”를 보고 k의 범위가 아주 큰 값이면 자연스럽게 이분 탐색이 떠오른다.
범위를 잡고 mid 이하의 원소 개수를 세서 조정하면 되겠지 했다.
그런데 금방 벽에 부딪혔다.
배수들이 서로 겹치기 때문에 단순히 “<= mid”인 값의 개수를 정확히 셀 수 없었다.
예를 들어,
- 12는 3×4로도, 4×3으로도 만들어질 수 있다.
이 중복이 정렬 순서를 교란시켜버리기 때문에,
이분 탐색으로는 정확한 순서를 구할 수 없었다.
사고 전환: 유한한 부분으로 줄이기
‘무한히 뻗어가니 답이 없다고 생각했지만,
k번째 작은 수는 어차피 유한한 구간 안에 존재한다.’
예를 들어 {1,2,3,4}에서 k = 29라면,
벡터 크기 n이 4이므로 k보다 큰 4의 배수 중 가장 작은 값은 32다.
즉, 8배수까지만 보면 된다.
그럼 가능한 값들은 다음과 같다:
여기서 정렬하면 29번째 원소는 24가 된다.
즉, 문제는 **무한한 수열이 아니라,
‘k보다 큰 벡터 크기의 배수까지 제한된 부분 문제’**로 축소할 수 있었다.
전환점: priority_queue(우선순위 큐)
문제를 다시 바라보다가 문득 priority_queue가 떠올랐다.
‘가장 큰 값들만 관리하면서 하나씩 줄여나가면 되지 않을까?’
그 순간 퍼즐이 맞춰졌다.
각 배수열의 마지막 값(가장 큰 배수)과 어떤 수의 배수인지 알 수 있는 수를 pair 형식으로 힙에 넣고,
가장 큰 수를 하나씩 꺼내며 줄여가면 된다.
예를 들어
형태로 넣어두면, top은 항상 가장 큰 수(32)가 된다.
- top을 꺼내서 4를 빼고 다시 넣는다 → {28,4}
- 다시 top을 꺼내서 4를 빼고 → {24,4}
- …이 과정을 k번째까지 반복하면,
마지막으로 꺼낸 값이 곧 k번째로 작은 값이다.
이 방식의 핵심은
- 배수 중복이 자연스럽게 처리되고,
- 항상 최대 n개의 값만 관리하기 때문에
k가 아무리 커도 O(n log n) 수준의 효율로 해결된다는 점이다.
시간복잡도 직관
- n = 벡터 크기
- k = 매우 큰 수 (최대 10⁸ 이상 가능)
- 하지만 힙에는 n개의 원소만 유지되므로,
각 pop/push가 O(log n), 전체는 O(n log n) 내에 해결 가능하다.
즉,
n이 10,000이고 k가 100,000,000이라도
최대 10,000번만 순회하면 된다.
예시: k = 29일 때의 과정
결과: 24
마무리하며
이 문제는 단순한 구현 문제가 아니라,
“무한을 유한으로 바꾸는 사고의 전환”이 필요한 문제였다.
처음엔 단순히 모든 배수를 나열하고 정렬하는 단순한 brute-force 접근이 떠올랐고,
그다음엔 이분 탐색으로 풀 수 있지 않을까 시도했지만 실패했다.
결국 핵심은 정렬을 뒤집어 시뮬레이션하는 우선순위 큐의 관점이었다.
이 경험을 통해 배운 점은 명확하다.
문제를 풀 때는 풀어야 할 데이터의 크기가 아니라,
“진짜 필요한 범위”를 먼저 찾아내야 한다.