문제 보기 · Level 3 · 2021 카카오 채용연계형 인턴십 · 정답률 42% · 푼 사람 7,420명 (2026-09-30 기준)1
문제
0 ~ n − 1행인 표에서 k행이 선택되어 있다. 명령은 네 가지다.
"U X": X칸 위의 행을 선택한다."D X": X칸 아래의 행을 선택한다."C": 선택된 행을 지우고 바로 아래 행을 선택한다. 지운 행이 마지막 행이면 바로 위 행을 선택한다."Z": 가장 최근에 지운 행을 원래 자리에 되살린다. 선택된 행은 그대로다.
모든 명령을 마친 뒤, 처음 표와 비교해 남아 있는 행은 O, 지워진 행은 X로 적은 문자열을 돌려준다.
- 5 ≤ n ≤ 1,000,000
- 명령 최대 200,000개, 모든 X의 합 ≤ 1,000,000
풀이
막히면 한 단계씩 펼친다.
1단계 · 작은 예로 손으로 풀기
5행 표에서 2행이 선택되어 있다. 명령은 C, C, Z다.
C: 2행을 지운다. 표는 0, 1, 3, 4행이고, 바로 아래였던 3행이 선택된다.C: 3행을 지운다. 표는 0, 1, 4행이고, 4행이 선택된다.Z: 가장 최근에 지운 3행을 되살린다. 표는 0, 1, 3, 4행이고, 선택은 여전히 4행이다.
결과는 OOXOO다. 되살릴 때 “3행은 원래 1행과 4행 사이에 있었다”를 알아야 한다.
2단계 · 가장 단순한 방법과 그 속도
살아 있는 행 번호를 파이썬 리스트에 두고 pop, insert로 지우고 되살린다. 가운데를 지우면 뒤의 행을 모두 한 칸씩 당기니 한 번에 \(O(n)\)이다. 명령 20만 번 × 행 100만 개면 최악 2,000억 번이라, 정확성 테스트(n ≤ 1,000)는 통과해도 효율성 테스트는 못 넘는다.
3단계 · 핵심 아이디어
- 행마다 위 이웃
prev와 아래 이웃nxt를 적는다. 처음에는 i의 위가 i − 1, 아래가 i + 1이다. 위가 없으면 −1, 아래가 없으면 n으로 적는다. - C: 위 이웃의 아래를 내 아래로, 아래 이웃의 위를 내 위로 바꾼다. 지워진 행 자신의 prev, nxt는 그대로 둔다. 지운 행 번호는 스택에 쌓는다.
- Z: 스택에서 꺼낸 행 x는 아직 “내 위는 p, 아래는 q”를 기억한다. p의 아래와 q의 위를 다시 x로 바꾸면 된다. 가장 최근에 지운 행부터 되살리니, p와 q가 지금 표에 살아 있다.
- U, D: 이웃을 X번 따라간다. X의 합이 100만 이하라 모두 합쳐도 100만 걸음이다.
- 답: 마지막에 스택에 남은 행이 지워진 행이다.
4단계 · 코드 흐름
- prev, nxt 리스트와 빈 스택을 만든다. 선택된 행 cur = k.
- 명령마다:
- U X, D X: prev나 nxt를 X번 따라간다.
- C: cur를 스택에 넣고, 양옆을 서로 잇는다. 아래 이웃이 있으면 그리로, 없으면 위 이웃으로 cur를 옮긴다.
- Z: 스택에서 꺼내 양옆이 다시 그 행을 가리키게 한다.
- 모두 O로 채운 뒤 스택에 남은 행만 X로 바꾼다.
5단계 · 파이썬 코드
def solution(n, k, cmd):
prev = [i - 1 for i in range(n)]
nxt = [i + 1 for i in range(n)]
removed = [] # 지운 행을 쌓는 스택
cur = k
for c in cmd:
if c[0] == "U":
for _ in range(int(c[2:])):
cur = prev[cur]
elif c[0] == "D":
for _ in range(int(c[2:])):
cur = nxt[cur]
elif c[0] == "C":
removed.append(cur)
p, q = prev[cur], nxt[cur]
if p != -1: nxt[p] = q # 위 이웃 → 내 아래
if q != n: prev[q] = p # 아래 이웃 → 내 위
cur = q if q != n else p # 마지막 행이면 위로
else: # Z
x = removed.pop()
p, q = prev[x], nxt[x] # x가 기억하는 이웃
if p != -1: nxt[p] = x
if q != n: prev[q] = x
ans = ["O"] * n
for x in removed:
ans[x] = "X"
return "".join(ans)
c[2:]:"U 10"에서 세 번째 글자부터, 곧"10"이다.c[-1]로 쓰면 두 자리 이상에서 틀린다.cur = q if q != n else p: 아래 이웃이 있으면 q, 없으면 p를 고르는 한 줄 조건식이다.
6단계 · 실행 추적
n = 8, k = 2, cmd = [“D 2”, “C”, “U 3”, “C”, “D 4”, “C”, “U 2”, “Z”, “Z”]
| 명령 | 선택된 행 | 표 상태 | 지운 행 스택 |
|---|---|---|---|
| D 2 | 4 | OOOOOOOO | [] |
| C | 5 | OOOOXOOO | [4] |
| U 3 | 1 | OOOOXOOO | [4] |
| C | 2 | OXOOXOOO | [4, 1] |
| D 4 | 7 | OXOOXOOO | [4, 1] |
| C | 6 | OXOOXOOX | [4, 1, 7] |
| U 2 | 3 | OXOOXOOX | [4, 1, 7] |
| Z | 3 | OXOOXOOO | [4, 1] |
| Z | 3 | OOOOXOOO | [4] |
“U 3”은 살아 있는 행만 센다. 5행에서 위로 3, 2, 1행을 거쳐 1행이 된다(4행은 지워져 건너뛴다). 답은 OOOOXOOO다.
7단계 · 복잡도와 흔한 실수
처음 리스트를 만드는 데 \(O(n)\), C·Z는 한 번에 \(O(1)\), U·D는 모두 합쳐 \(O(\text{X의 합})\)이다. 행 100만 개, 명령 20만 개로 재 보니 0.1초가 안 걸렸다.
- 파이썬 리스트의
pop·insert로 풀어 효율성 테스트에서 시간 초과가 난다. - 지운 행의 prev, nxt까지 지워 되살릴 수 없게 만든다.
- 마지막 행을 지운 뒤 위 행을 선택하는 규칙을 빠뜨린다.
- Z에서 선택된 행까지 바꾼다. Z는 선택을 바꾸지 않는다.
-
프로그래머스 스쿨, “표 편집” (https://school.programmers.co.kr/learn/courses/30/lessons/81303), 2026-09-30 조회. ↩