PS❯BOJ2022-04-263 min read
[BOJ 1406] 에디터 (Java) - 0.3초 시간 초과 해결을 위한 두 개의 스택(Two Stacks)
백준 1406번(에디터) 문제에서 0.3초의 빡빡한 시간 제한으로 인해 LinkedList 인덱스 접근이 시간 초과되는 이유를 분석하고, 커서 기준 좌·우 두 개의 스택으로 모든 명령을 O(1)에 처리하는 풀이를 정리합니다.
#BOJ#스택#자료구조#구현#Java#Silver II
문제 정보
- 문제 링크: 백준 1406번 - 에디터
- 난이도: Silver II
- 시간 / 메모리 제한:
0.3초 (Java 추가 시간 없음)/512 MB
핵심 조건 요약
- 초기 문자열(길이
N ≤ 100,000)과 명령어 개수M(1 ≤ M ≤ 500,000)이 주어지며, 명령어 수행 전 커서는 문장의 맨 뒤에 위치합니다. - 지원 명령어:
L: 커서를 왼쪽으로 한 칸 이동 (맨 앞이면 무시)D: 커서를 오른쪽으로 한 칸 이동 (맨 뒤이면 무시)B: 커서 왼쪽 문자 삭제 (맨 앞이면 무시)P $:$문자를 커서 왼쪽에 추가
트러블슈팅: 왜 LinkedList / StringBuilder.insert는 시간 초과가 날까?

처음에는 중간 삽입/삭제가 O(1)이라는 점만 보고 LinkedList를 사용했지만 연달아 시간 초과가 발생했습니다.
LinkedList.add(index, element)/remove(index)의 함정: 연결 리스트는 노드 포인터를 가지고 있을 때만 삽입/삭제가O(1)이지, 인덱스cursor를 넘겨 접근하면 매번 처음(또는 끝)부터 노드를 따라가야 하므로 **O(N)**이 걸립니다!StringBuilder.insert(cursor, ...)/deleteCharAt(cursor): 내부 배열을 시프트해야 하므로 마찬가지로 **O(N)**이 소요됩니다.- 명령어 개수
M = 500,000, 문자열 길이 최대600,000이므로O(M × N)은 0.3초 제한에서 무조건 시간 초과가 됩니다.
접근 아이디어: 커서를 기준으로 좌·우 두 개의 스택(left, right)
커서의 이동과 편집을 모두 **O(1)**로 처리하려면 커서 왼쪽 문자열을 담는 left 스택과 커서 오른쪽 문자열을 담는 right 스택 두 개를 두면 됩니다.
예시: "abc|de" (커서가 c와 d 사이)
left 스택 (bottom → top): [a, b, c]
right 스택 (top → bottom): [d, e]
| 명령 | 동작 | 시간 복잡도 |
|---|---|---|
L |
left에서 pop하여 right에 push |
O(1) |
D |
right에서 pop하여 left에 push |
O(1) |
B |
left에서 pop (버림) |
O(1) |
P $ |
left에 $를 push |
O(1) |
모든 명령 처리가 끝난 뒤에는 left 스택의 원소를 바닥부터 순서대로 붙이고, right 스택은 pop하면서 이어 붙이면 완성된 문자열이 됩니다.
Java 풀이 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Stack;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Stack<Character> left = new Stack<>();
Stack<Character> right = new Stack<>();
String init = br.readLine();
for (int i = 0; i < init.length(); i++) {
left.push(init.charAt(i));
}
int queryCount = Integer.parseInt(br.readLine());
for (int i = 0; i < queryCount; i++) {
String line = br.readLine();
char query = line.charAt(0);
if (query == 'L' && !left.isEmpty()) {
right.push(left.pop());
} else if (query == 'D' && !right.isEmpty()) {
left.push(right.pop());
} else if (query == 'B' && !left.isEmpty()) {
left.pop();
} else if (query == 'P') {
left.push(line.charAt(2));
}
}
StringBuilder ans = new StringBuilder();
for (Character ch : left) {
ans.append(ch);
}
while (!right.isEmpty()) {
ans.append(right.pop());
}
System.out.println(ans);
}
}
성능 팁:
Stack<Character>대신ArrayDeque<Character>를 쓰면 동기화(synchronized) 오버헤드가 없어 더 빠르고,ListIterator를 활용한LinkedList역시 커서 포인터를 직접 유지하므로O(M)에 통과할 수 있습니다.
채점 결과

복잡도 분석
- 시간 복잡도:
O(N + M)— 초기 문자열 적재O(N)+M개 명령당O(1)+ 최종 출력O(N + M) - 공간 복잡도:
O(N + M)— 두 스택에 저장되는 최대 문자 수N + M
← PREVIOUS LOG
[BOJ 2504] 괄호의 값 (Java) - 분배법칙과 스택(Stack)으로 괄호 수식 계산하기
NEXT LOG →[BOJ 16198] 에너지 모으기 (Java) - 경로 압축(next[])을 접목한 백트래킹 풀이
COMMENTS // GITHUB DISCUSSIONS (GISCUS)