코딩테스트

[백준] 16139 - 인간-컴퓨터 상호작용

miniberry 2026. 3. 20. 12:30

1. 문제 요약

문자열 S가 주어지고, 여러 개의 질의가 들어온다.

각 질의는 다음과 같다:

alpha[l][r]
 

👉 구간 [l, r]에서 문자 alpha가 몇 번 등장하는지 구하기


2. 처음 접근 (딕셔너리 + 완전탐색)

처음에는 문자별 인덱스를 저장하고, 매 쿼리마다 직접 확인했다.

panels = dict()

for i, c in enumerate(s):
    if c not in panels:
        panels[c] = [i]
    else:
        panels[c].append(i)

for k in panels[alpha]:
    if l <= k <= r:
    	count += 1
 
❌ 문제점
  • 질의마다 리스트를 순회 → 최악 O(N)
  • 시간초과 발생

3. 개선 시도 (누적합 / Prefix Sum)

그래서 각 알파벳별 누적합 배열을 만들었다.

panels = [[0] * (n+1) for _ in range(26)]

for i in range(n):
    for j in range(26):
        panels[j][i+1] = panels[j][i]
        panels[ord(s[i]) - ord('a')][i+1] += 1
 

그리고 질의는:

panels[k][r+1] - panels[k][l]
 

👉 시간복잡도:

  • 전처리: O(26N)
  • 질의: O(1)

그런데도 100점이 안 나옴…

이게 핵심 포인트다.

👉 알고리즘은 맞는데 Python에서 통과 못함

이유 1) 복사 비용

 
for j in range(26):
    panels[j][i+1] = panels[j][i]
 
  • 문자 하나마다 26번 복사
  • 총 연산량: 약 26 × N

👉 Python에서는 이 "복사"가 생각보다 무겁다


이유 2) 메모리 부담

 
26 × (n+1) 크기의 리스트
 
  • Python int는 객체라서 메모리 사용량 큼
  • 테스트 케이스에 따라 성능 저하 발생

⚡ 해결 방법 1 — bisect (정석)

문자 위치만 저장하고, 이진 탐색으로 개수를 구하는 방식

 
from bisect import bisect_left, bisect_right

panels = [[] for _ in range(26)]

for i, ch in enumerate(s):
    panels[ord(ch) - 97].append(i)

    left = bisect_left(arr, l)
    right = bisect_right(arr, r)

    print(right - left)
 

✔ 특징

  • 전처리: O(N)
  • 질의: O(log N)
  • 메모리 효율 좋음

👉 Python에서는 가장 안정적인 정답


⚡ 해결 방법 2 — Prefix 최적화 (내가 통과한 방법)

누적합 방식 유지하면서, 입출력 + 연산 최적화 적용

 
import sys

readline = sys.stdin.buffer.readline

s = readline().rstrip()
q = int(readline())
n = len(s)

panels = [[0] * (n + 1) for _ in range(26)]

for i, ch in enumerate(s, 1):
	prev = i - 1
for row in panels:
    row[i] = row[prev]
    panels[ch - 97][i] += 1

out = []

for _ in range(q):
    alpha, l, r = readline().split()
    l = int(l)
    r = int(r) + 1
    k = alpha[0] - 97
    out.append(str(panels[k][r] - panels[k][l]))

    sys.stdout.write("\n".join(out))

4. 적용한 최적화 포인트

 1) sys.stdin.buffer.readline

  • 입력 속도 개선

 2) print → sys.stdout.write

  • 출력 병목 제거

 3) bytes 활용

 
s = readline().rstrip()
 
  • ord() 없이 바로 연산 가능

 4) enumerate(..., 1)

  • 인덱스 계산 줄이기

5. 결론

“알고리즘이 맞다고 해서 Python에서 통과하는 건 아니다”

이 문제에서 배운 것:

  • 이론적으로는 prefix sum이 더 빠름 (O(1) 질의)
  • 하지만 Python에서는
    • 리스트 복사 비용
    • 메모리 구조
      때문에 느려질 수 있음
방법결과
완전탐색 ❌ 시간초과
prefix sum ⚠ 일부 테스트 실패
prefix + 최적화 ✅ 통과
bisect ✅ 가장 안정

 

'코딩테스트' 카테고리의 다른 글

[백준] 16165번 - 걸그룹 마스터 준석이  (0) 2026.03.24
[백준] 13414번 - 수강신청  (0) 2026.03.23
[백준] 2559 - 수열  (0) 2026.03.19
[백준] 12865 - 평범한 배낭  (0) 2026.03.18
[백준] 9251 - LCS  (0) 2026.03.17