처음엔 split() 없이 풀어보려 했다. 그게 통과는 됐는데, 시간 초과가 났다. 그 다음엔 왜 느렸는지까지 따라가 봤다.
문제 — 백준 10828 스택
정수를 저장하는 스택을 구현한다. 명령이 주어지면 각각 처리하고 결과를 출력한다.
| 항목 | 내용 |
|---|---|
| 문제 번호 | 백준 10828 |
| 난이도 | 실버 4 |
| 분류 | 자료구조, 스택 |
| 명령 종류 | push X, pop, size, empty, top |
| 입력 범위 | 명령 수 N ≤ 10,000 |
명령별 동작
| 명령 | 동작 | 출력 |
|---|---|---|
push X |
정수 X를 스택에 넣는다 | 없음 |
pop |
맨 위 원소를 꺼낸다. 비어 있으면 -1 | 꺼낸 값 또는 -1 |
size |
스택에 들어 있는 원소 수 | 원소 개수 |
empty |
비어 있으면 1, 아니면 0 | 1 또는 0 |
top |
맨 위 원소를 확인한다. 비어 있으면 -1 | 맨 위 값 또는 -1 |
핵심 아이디어 — 리스트를 스택으로
파이썬 리스트는 뒤쪽에서 넣고 빼기가 O(1)이다. 스택의 "맨 위"를 리스트의 맨 뒤로 보면 된다.
| 스택 연산 | 리스트로 구현 | 예시 |
|---|---|---|
| push X | stack.append(X) |
[1,2] → push 3 → [1,2,3] |
| pop | stack.pop() |
[1,2,3] → pop → 3 반환, [1,2] 남음 |
| top | stack[-1] |
[1,2,3] → top → 3 (꺼내지 않음) |
| size | len(stack) |
[1,2,3] → 3 |
| empty | stack == [] |
[] → 1, [1] → 0 |
1단계 — 가장 단순한 구현
split() 없이, sys 모듈 없이, 문자열을 통째로 받아서 처리한다.
n = int(input())
stack = []
for _ in range(n):
command = input()
if command[:4] == "push":
num = int(command[5:])
stack.append(num)
elif command == "pop":
if stack == []:
print(-1)
else:
print(stack.pop())
elif command == "size":
print(len(stack))
elif command == "empty":
if stack == []:
print(1)
else:
print(0)
elif command == "top":
if stack == []:
print(-1)
else:
print(stack[-1])
입력을 문자열로 받는 방식
command = input()으로 한 줄을 그대로 받으면 문자열이 저장된다.
| 입력 | command 값 |
|---|---|
push 3 |
"push 3" |
pop |
"pop" |
top |
"top" |
command[:4] == "push" — 슬라이싱으로 명령 구분
파이썬 문자열은 인덱스로 자를 수 있다. 문자열[시작:끝] 형태로 쓰면 잘라낸 부분 문자열이 나온다.
command = "push 3"
# 문자 인덱스
# p=0 u=1 s=2 h=3 ' '=4 3=5
command[:4] # "push"
command[5:] # "3"
| 슬라이싱 | 의미 | "push 3"일 때 결과 |
|---|---|---|
command[:4] |
앞 4글자 | "push" |
command[5:] |
6번째 글자부터 끝까지 | "3" → int() 씌우면 정수 3 |
command == "push"로 비교하면 "push 3"이 들어왔을 때 False가 된다. 뒤에 숫자가 붙기 때문이다. 그래서 앞 4글자만 잘라서 비교한다.
각 명령어 구현 설명
if command[:4] == "push":
num = int(command[5:]) # "3" → 3
stack.append(num) # 스택 맨 뒤에 추가
push: 숫자를 꺼내 스택에 넣는다.
elif command == "pop":
if stack == []:
print(-1)
else:
print(stack.pop()) # 맨 뒤 원소를 꺼내고 반환
pop: 빈 리스트에서 pop()을 호출하면 오류가 나므로 반드시 먼저 확인한다.
elif command == "top":
if stack == []:
print(-1)
else:
print(stack[-1]) # 꺼내지 않고 마지막 값만 확인
top: stack[-1]은 맨 뒤 원소를 꺼내지 않고 읽기만 한다. pop()과 다르다.
동작 시뮬레이션
입력이 아래와 같을 때 스택이 어떻게 변하는지 따라가 본다.
7
push 1
push 2
top
size
empty
pop
pop
| 명령 | 처리 | 스택 상태 | 출력 |
|---|---|---|---|
| push 1 | append(1) | [1] | — |
| push 2 | append(2) | [1, 2] | — |
| top | stack[-1] | [1, 2] | 2 |
| size | len(stack) | [1, 2] | 2 |
| empty | stack == []? | [1, 2] | 0 |
| pop | stack.pop() | [1] | 2 |
| pop | stack.pop() | [] | 1 |
2단계 — 시간 초과 원인 분석
1단계 코드를 제출하면 시간 초과가 난다. 이유는 두 가지다.
원인 1 — input()이 느리다
파이썬의 input()은 내부적으로 처리가 많아 명령이 수천 개 이상일 때 느려진다.sys.stdin.readline()은 버퍼에서 직접 읽어서 더 빠르다.
원인 2 — print()를 매번 호출하면 느리다
출력 명령이 많으면 print()를 반복적으로 호출하는 것 자체가 부담이 된다.
결과를 리스트에 모아두었다가 마지막에 한 번만 출력하면 빠르다.
| 구분 | 느린 방식 | 빠른 방식 |
|---|---|---|
| 입력 | input() |
sys.stdin.readline() |
| 출력 | print() 매번 호출 |
result.append() 후 마지막에 한 번 |
readline()과 rstrip() 이해
sys.stdin.readline()은 줄 끝에 \n이 붙어서 들어온다. 이것을 제거해야 조건 비교가 제대로 된다.
sys.stdin.readline() # "push 3\n" ← \n이 붙어 있음
sys.stdin.readline().rstrip() # "push 3" ← rstrip()으로 오른쪽 공백·줄바꿈 제거
| 함수 | 동작 | "pop\n" 에 적용하면 |
|---|---|---|
rstrip() |
오른쪽 공백·줄바꿈 제거 | "pop" |
strip() |
양쪽 공백·줄바꿈 제거 | "pop" |
결과 모아서 한 번에 출력하는 방식
# 느린 방식 — 출력이 생길 때마다 호출
print(stack.pop()) # 매번 I/O 발생
# 빠른 방식 — 리스트에 쌓아두었다가 마지막에 한 번
result.append(str(stack.pop()))
# ...
sys.stdout.write("\n".join(result)) # I/O 1번만 발생
"\n".join(result)는 리스트 원소들을 줄바꿈 문자로 이어 붙여 하나의 문자열로 만든다.
result = ["2", "2", "0", "2", "1"]
"\n".join(result)
# "2\n2\n0\n2\n1"
# 출력하면
# 2
# 2
# 0
# 2
# 1
최종 코드
1단계 코드 구조를 그대로 유지하면서 느린 부분 두 곳만 교체했다.
# 스택 — 백준 10828
# https://www.acmicpc.net/problem/10828
import sys
n = int(sys.stdin.readline())
stack = []
result = []
for _ in range(n):
command = sys.stdin.readline().rstrip()
if command[:4] == "push":
num = int(command[5:])
stack.append(num)
elif command == "pop":
if stack == []:
result.append("-1")
else:
result.append(str(stack.pop()))
elif command == "size":
result.append(str(len(stack)))
elif command == "empty":
if stack == []:
result.append("1")
else:
result.append("0")
elif command == "top":
if stack == []:
result.append("-1")
else:
result.append(str(stack[-1]))
sys.stdout.write("\n".join(result))
1단계 vs 최종 — 달라진 곳
| 항목 | 1단계 (시간 초과) | 최종 (통과) | 이유 |
|---|---|---|---|
| 입력 | input() |
sys.stdin.readline().rstrip() |
readline이 더 빠름 + \n 제거 |
| 출력 방식 | print() 바로 호출 |
result.append() 후 일괄 출력 |
I/O 횟수를 1번으로 줄임 |
| 출력 호출 | print(값) |
sys.stdout.write("\n".join(result)) |
버퍼에 한 번에 씀 |
| 알고리즘 | 동일 | 동일 | 로직은 전혀 안 바뀜 |
str()로 문자열 변환을 해야 한다."\n".join()은 문자열 리스트만 처리할 수 있어서, 정수가 섞여 있으면 TypeError가 난다.초보자가 자주 하는 실수
| 실수 | 틀린 코드 | 올바른 코드 |
|---|---|---|
| 빈 스택에서 pop | stack.pop() 바로 호출 |
먼저 stack == [] 검사 |
| push 숫자를 문자열로 넣기 | stack.append(command[5:]) |
stack.append(int(command[5:])) |
| push 비교를 ==으로 | command == "push" |
command[:4] == "push" |
| result에 정수 그대로 넣기 | result.append(stack.pop()) |
result.append(str(stack.pop())) |
| rstrip() 빠뜨리기 | readline()만 씀 |
readline().rstrip() |
시간복잡도
| 연산 | 복잡도 | 근거 |
|---|---|---|
| push (append) | O(1) 평균 | 리스트 맨 뒤 추가 |
| pop (pop) | O(1) | 리스트 맨 뒤 제거 |
| size (len) | O(1) | 파이썬 리스트는 길이를 별도 저장 |
| empty, top | O(1) | 인덱스 접근 또는 len 비교 |
| 전체 (N개 명령) | O(N) | 각 명령이 O(1) |
코드 로직은 처음부터 맞았다. 달라진 건 입력과 출력 방식뿐이었다. 알고리즘이 맞아도 I/O에서 시간 초과가 나는 경우가 있다는 걸 이번에 확인했다.
🌿
'크래프톤 정글 > 정글에서 문제풀기' 카테고리의 다른 글
| [정글 알고리즘] - [중] - 백준2470- 투포인터 - 두 용액 (0) | 2026.03.16 |
|---|---|
| [정글 알고리즘] - [중] - 백준2493 탑 (0) | 2026.03.16 |
| [정글 알고리즘]-[하]-백준 2164 카드2 (0) | 2026.03.16 |
| [정글 알고리즘]-[하]-백준 9933 - 민균이의 비밀번호 (0) | 2026.03.16 |
| [정글 베이직 19] — 해시 테이블 · 성적 관리 (0) | 2026.03.13 |