본문 바로가기
Back-end & 알고리즘

코딩테스트 문자열 인덱스 실수(Out of Bounds) 줄이는 4가지 방어적 코드 패턴

by CodeByJin 2026. 5. 17.
반응형

코딩테스트에서 0과 1 사이를 헤매다 떨어지는 이유

현업에서 대규모 트래픽을 처리하는 백엔드 아키텍처를 설계하고, 수억 개의 토큰을 다루는 LLM 파이프라인을 구축하는 베테랑 개발자들도 코딩테스트 시험장만 들어가면 얼어붙곤 합니다. 특히 문자열(String) 처리 문제에서 빈번하게 발생하는 '인덱스 하나 차이'의 오류는 수많은 지원자를 탈락의 고배로 몰아넣는 주범입니다. 평소에는 IDE의 자동 완성 기능과 컴파일러의 친절한 경고 메시지에 의존하다가, 메모장과 유사한 척박한 코딩 환경에 노출되면 눈이 멀어버리는 것입니다. 문자열 슬라이싱을 하다가 IndexOutOfBoundsException을 마주하거나, 반복문의 종료 조건에서 <=와 <를 헷갈려 1초 차이로 효율성 테스트를 통과하지 못하는 상황은 결코 남의 일이 아닙니다.

 

이러한 실수는 단순히 '컨디션이 나빴다'거나 '실수를 했다'는 핑계로 넘어갈 문제가 아닙니다. 근본적으로 문자열을 메모리 상에서 어떻게 다루는지, 그리고 경계 조건(Boundary Condition)을 설계할 때 어떤 수학적 모델을 머릿속에 그리고 있는지에 대한 기본기 부족에서 기인합니다. 15년 넘게 수많은 레거시 코드를 뜯어고치고 주니어들의 코드를 리뷰하면서 느낀 점은, 인덱스 실수를 줄이는 비법은 더 많은 문제를 푸는 것이 아니라 문자열을 바라보는 프레임 자체를 바꾸는 데 있다는 사실입니다. 오늘 이 글에서는 문자열 알고리즘 문제에서 인덱스 에러를 원천 봉쇄할 수 있는 경험적이고 구체적인 전략들을 공유하고자 합니다.

문자열 인덱스 오류가 발생하는 3가지 근본적 원인

문자열 문제에서 인덱스 실수가 발생하는 이유는 명확합니다. 인간의 뇌는 기본적으로 '1'부터 시작하는 카운팅(Ordinal Numbers)에 익숙한 반면, 컴퓨터는 메모리 오프셋(Offset) 개념인 '0'부터 시작하는 인덱싱을 사용하기 때문입니다. 이러한 인지적 불일치는 복잡한 조건문과 결합할 때 폭발적인 시너지를 내며 버그를 만들어냅니다.

1. 이상과 미만의 모호한 경계 설계

문자열의 하위 구간(Substring)을 추출할 때 언어별로 구현된 API의 특성을 정확히 모르면 반드시 사달이 납니다. 예를 들어 자바의 substring(int beginIndex, int endIndex)나 파이썬의 string[start:end]는 모두 시작 인덱스는 '포함(Inclusive)'하고 끝 인덱스는 '제외(Exclusive)'하는 구조를 취합니다. 즉, [start, end) 형태의 반개구간(Half-open interval)입니다. 문제를 풀 때 머릿속으로 '몇 번째 글자부터 몇 번째 글자까지'라고 정수 기반으로만 생각하다 보면, 반복문 제어 변수에 1을 더해야 할지 빼야 할지 헷갈리게 됩니다.

2. 문자열 수정 시 발생하는 가변(Mutable) 객체의 함정

문자열을 순회하면서 특정 조건에 맞는 문자를 삭제하거나 삽입할 때, 원본 문자열의 길이가 실시간으로 변한다는 사실을 간과하는 경우가 많습니다. 인덱스 i를 증가시키며 작업을 수행하는데, 정작 문자열의 전체 길이는 줄어들고 있다면 뒤쪽 데이터들의 인덱스가 앞으로 밀리면서 의도치 않은 문자를 건너뛰거나 인덱스 범위를 초과하게 됩니다. 이건 직접 운영해 보면 체감이 확 됩니다. 실무에서도 가변 배열을 다룰 때 발생하는 가장 흔한 동시성 및 논리 버그 중 하나입니다.

3. 투 포인터 및 슬라이딩 윈도우의 갱신 타이밍 불일치

연속된 부분 문자열을 탐색하는 투 포인터(Two Pointers) 알고리즘이나 슬라이딩 윈도우(Sliding Window) 알고리즘을 사용할 때, 포인터를 이동하는 타이밍과 윈도우의 크기를 계산하는 타이밍이 어긋나면 무조건 에러가 발생합니다. 포인터를 먼저 증가시키고 조건을 검사할 것인가, 아니면 검사를 하고 나서 포인터를 움직일 것인가에 대한 일관된 원칙이 없으면 코드가 누더기가 되고 결국 예외 케이스(Edge Case)에서 무너집니다.

코딩테스트인덱스오류 - 생성형 ai 이미지

구조적 분석: 인덱스 오류를 막는 4가지 오프셋 모델

실수를 줄이기 위해서는 코드를 짜기 전 설계 단계에서 인덱스를 바라보는 명확한 수학적, 시각적 모델을 수립해야 합니다. 무작정 IDE에 타이핑을 시작하는 것은 버그를 키우는 지름길입니다.

오프셋 모델명 핵심 개념 주요 활용 문제 유형
반개구간 모델 [시작, 끝) 형태로 설계하여 '끝 - 시작 = 길이' 공식 성립 Substring 추출, 문자열 분할 탐색
가상 경계선 모델 인덱스를 문자가 아닌 '문자 사이의 칸막이'로 인식 문자열 삽입, 팰린드롬 중심 확장 탐색
역방향 순회 모델 끝에서부터 앞으로 탐색하여 인덱스 밀림 현상 방지 특정 문자 조건부 삭제 및 치환
모듈러 연산 모델 나머지 연산(%)을 이용해 인덱스를 원형 구조로 순환 회전형 문자열, 매칭 알고리즘, 슬라이딩 윈도우

위 테이블에 제시된 모델 중 자신에게 가장 잘 맞는 방식을 골라 일관되게 적용해야 합니다. 특히 반개구간 모델은 코딩테스트뿐만 아니라 대규모 데이터를 청크(Chunk) 단위로 나누어 처리하는 분산 컴퓨팅 환경에서도 오프셋 실수를 줄여주는 검증된 방식입니다.

실무형 체크리스트: 시험장에서 멘탈을 잡아줄 루틴

문제를 풀 때 인덱스 때문에 코드가 꼬이기 시작하면 심박수가 올라가고 눈앞이 캄캄해집니다. 이때 주먹구구식으로 +1이나 -1을 코드 여기저기에 붙여보며 기도를 올리는 지원자들이 많습니다. 다행히 합격하더라도 그런 코드는 현업에서 심각한 장애를 유발하는 시한폭탄이 됩니다. 다음 체크리스트를 문제 풀이 루틴에 이식하십시오.

  • 길이가 0이거나 1인 극단적 입력값(Edge Case)을 가장 먼저 대입해 보았는가?
  • 반복문의 조건식에서 i < 문자열.length()와 i <= 문자열.length() - 1 중 어떤 것을 선택했는지 명확한 이유가 있는가?
  • right - left 연산의 결과가 가리키는 것이 구간 내 원소의 개수인가, 아니면 마지막 인덱스인가?
  • 문자열을 배열이나 리스트로 변환한 뒤 인덱스를 조작할 때, 원본 데이터의 불변성(Immutability)이 유지되고 있는가?

PPA: 문자열을 캐릭터 배열로 변환해서 풀면 성능 손해가 크지 않나요?

많은 지원자들이 "자바나 파이썬에서 String을 char[]나 list로 매번 변환하면 메모리와 시간 측면에서 대가가 너무 크지 않냐"고 묻습니다. 결론부터 말씀드리면, 코딩테스트 환경에서는 변환하는 이득이 실보다 훨씬 큽니다. 물론 메모리가 극도로 제한된 임베디드 시스템이나 실시간 초저지연(Ultra-Low Latency) 전송 시스템이라면 매번 새로운 배열을 할당하는 행위가 GC(Garbage Collector)에 부담을 주어 수 밀리초(ms)의 지연을 유발할 수 있습니다.

 

하지만 알고리즘 문제 해결(PS) 관점에서는 이야기가 다릅니다. 자바의 String.charAt(idx) 메서드는 호출할 때마다 내부적으로 범위 체크(Bounds Check) 로직을 수행합니다. 루프 안에서 수백만 번 charAt을 호출하는 것보다, 최초에 toCharArray()를 통해 프리미티브 배열을 단 한 번 메모리에 올리는 것이 CPU 캐시 히트율(Cache Hit Rate) 측면에서 오히려 더 유리할 때가 많습니다. 무엇보다 배열로 변환하면 인덱스 접근 괄호(arr[idx])를 사용할 수 있어 가독성이 비약적으로 상승하고, 이는 곧 실수를 줄이는 결정적인 요인이 됩니다. 푼 문제를 아깝게 틀리는 것보다 데이터 변환 비용 몇 밀리초를 지불하는 것이 훨씬 남는 장사입니다.

인덱스 버그를 원천 차단하는 코드 패턴 기법

말과 이론은 쉽습니다. 실제로 코드를 작성할 때 인덱스 실수를 물리적으로 방지할 수 있는 방어적 코드 패턴을 언어별 예시와 함께 살펴보겠습니다.

1. 반개구간 [Start, End) 가이드라인 준수

어떤 구간의 문자열을 잘라내거나 조작할 때, 변수의 의미를 고정해야 합니다. start는 첫 번째 문자의 인덱스로, end는 포함되지 않는 경계선(즉, 마지막 문자의 인덱스 + 1)으로 정의하는 습관을 들이십시오. 이렇게 하면 해당 구간의 길이는 무조건 end - start가 됩니다. +1을 해야 할지 말아야 할지 고민할 필요가 완전히 사라집니다.

// Java 예시: 반개구간 원칙을 적용한 슬라이딩 윈도우 패턴
int start = 0;
int end = 0;
int maxLen = 0;
while (end < str.length()) {
    // 윈도우 확장
    char innerChar = str.charAt(end);
    end++;
    // 조건에 따른 윈도우 축소 로직 수반 가능
    // ...
    // 반개구간 구조이므로 별도의 +1 없이 직관적인 길이 계산 가능
    maxLen = Math.max(maxLen, end - start);
}

2. 역방향 루프를 통한 인덱스 밀림 현상 방지

문자열에서 특정 조건을 만족하는 글자를 지우거나 변경해야 할 때, 앞에서부터 순회하면 인덱스 관리가 지옥으로 변합니다. 이때는 대형마트에서 줄을 서 있는 사람들을 뒤에서부터 골라내어 퇴장시키는 것처럼, 루프를 역방향(length - 1부터 0까지)으로 돌려야 합니다. 앞쪽의 인덱스 구조가 그대로 유지되므로 인덱스가 꼬일 일이 전혀 없습니다.

# Python 예시: 역방향 순회를 통한 안전한 요소 조작
# 특정 조건을 만족하는 글자를 만났을 때 안전하게 처리하는 메커니즘
data = list(input_string)
for i in range(len(data) - 1, -1, -1):
    if data[i] == 'X':
    # 뒤에서부터 작업하므로 앞쪽 원소들의 오프셋(i)에는 아무런 영향이 없음
del data[i]

결국 어떤 선택을 해야 하는가?

코딩테스트의 문자열 문제에서 인덱스 실수를 극복하는 방법은 화려한 알고리즘을 외우는 것이 아닙니다. 본인이 코드를 짤 때 인덱스를 제어하는 일관된 철학이 있느냐 없느냐의 싸움입니다. 무턱대고 수식을 수정해가며 정답 맞추기 식 컴파일을 반복하는 버릇을 버리지 못하면, 복잡도가 조금만 올라가도 무너집니다.

 

만약 본인이 투 포인터나 서브스트링 문제에서 유독 자주 틀린다면, 오늘부터 모든 구간 변수를 반개구간 [Start, End) 체계로 통일하십시오. 그리고 문자열 자체를 직접 조작하기보다는 불변 객체의 특성을 인정하고 새로운 공간에 정답을 빌드업해 나가는 방식을 선택하십시오. 시간 복잡도가 허용하는 한, 문자열을 원시 배열로 변환하여 제어하는 것이 가독성과 디버깅 편의성 측면에서 압도적인 이득을 가져다줍니다. 현란한 테크닉보다는 흔들리지 않는 경계 조건 수립 규칙 하나가 여러분을 합격으로 인도할 것입니다.

 

코딩테스트부터 대용량 로그 분석까지, 투 포인터(Two Pointers) 패턴의 모든 것

코딩테스트 문제를 풀다 보면 "분명히 정답은 알겠는데 시간 초과(TLE) 때문에 미치겠다"는 순간이 온다. 특히 배열이나 리스트 같은 선형 자료구조에서 데이터를 뒤지는 상황이 그렇다. 우리는

byteandbit.tistory.com

* 본 포스팅에 사용된 이미지는 생성형 AI를 통해 생성된 이미지입니다. *

반응형