본문 바로가기
카테고리 없음

투 포인터 알고리즘: O(N)으로 끝내는 배열 탐색 최적화

by tech-korea 2026. 4. 14.

투 포인터 알고리즘: O(N)으로 끝내는 배열 탐색 최적화

소프트웨어 개발과 알고리즘 문제 해결 과정에서 시간 복잡도(Time Complexity) 최적화는 성능의 당락을 결정짓는 핵심 요소입니다. 특히 배열(Array)이나 리스트(List)와 같은 선형 자료구조에서 특정 조건을 만족하는 구간이나 쌍을 찾는 문제는 실무에서 매우 빈번하게 발생합니다. 대다수의 초급 개발자는 이를 해결하기 위해 이중 반복문을 활용한 O(N²)의 완전 탐색(Brute-force) 방식을 떠올리지만, 데이터의 크기가 수십만 건을 넘어서는 환경에서는 처리 속도 저하로 인해 시스템 전체에 병목 현상을 야기할 수 있습니다. 이러한 성능적 한계를 극복하고 연산 효율을 극대화하기 위해 고안된 기법이 바로 투 포인터(Two Pointers) 알고리즘입니다. 본 글에서는 투 포인터의 기술적 메커니즘과 실전 활용 전략을 상세히 분석합니다.

1. 투 포인터(Two Pointers) 알고리즘의 핵심 기술 원리와 유형

투 포인터 알고리즘은 리스트의 요소에 접근하기 위해 두 개의 참조 지점, 즉 포인터(Pointer)를 설정하고 이를 특정 규칙에 따라 이동시키며 문제를 해결하는 방식입니다. 이 알고리즘의 핵심은 불필요한 반복 연산을 제거하여 전체 배열을 단 한 번의 순회(Pass)만으로 탐색을 완료하는 데 있습니다. 이는 "긴 끈의 양 끝을 잡고 원하는 길이를 찾기 위해 두 손을 안쪽이나 바깥쪽으로 옮기며 조절하는 것"과 같아서, 중첩 루프를 사용하지 않고도 목표 데이터를 효과적으로 찾아낼 수 있습니다. 투 포인터 기법은 포인터의 배치 방식에 따라 크게 두 가지 유형으로 분류됩니다.

대표적인 두 가지 포인터 배치 전략

  • 대향 방향 포인터(Opposite Direction): 배열의 양 끝(시작점과 끝점)에서 포인터를 설정하고 서로를 향해 좁혀 들어오는 방식입니다. 주로 정렬된 배열에서 두 수의 합이 특정 타겟값과 일치하는 쌍을 찾을 때 사용됩니다.
  • 동일 방향 포인터(Same Direction): 두 포인터가 모두 시작점에서 출발하여 조건에 따라 앞서거니 뒤서거니 이동하는 방식입니다. 주로 가변적인 구간의 합이나 연속된 부분 수열의 특성을 파악할 때 활용되며, 흔히 '슬로우 포인터(Slow Pointer)'와 '패스트 포인터(Fast Pointer)' 기법으로 불리기도 합니다.

이 기법이 유효하기 위해서는 대개 배열이 정렬(Sorting)되어 있거나, 포인터의 이동에 따른 결과값의 변화가 단조성(Monotonicity)을 띠어야 한다는 전제 조건이 필요합니다. 포인터가 한 번의 루프 내에서만 이동하므로, 자료의 개수가 N일 때 O(N)의 시간 복잡도를 보장하며 이는 대규모 데이터 처리 시스템에서 비약적인 성능 향상을 이끌어내는 근거가 됩니다.

2. 실무적 활용: 연속 부분 수열과 특정 합 찾기 전략

투 포인터 알고리즘이 가장 빛을 발하는 순간은 연속된 데이터 구간의 합(Subarray Sum)을 처리할 때입니다. 예를 들어, 양의 정수로 이루어진 배열에서 합이 M이 되는 부분 수열의 개수를 구해야 하는 상황을 가정해 보겠습니다. 단순 반복문으로는 모든 시작점과 끝점의 조합을 계산해야 하므로 연산량이 급격히 늘어나지만, 투 포인터 기법을 적용하면 상황이 달라집니다. '시작점(start)'과 '끝점(end)' 포인터를 유동적으로 조절하며 현재의 합이 M보다 작으면 end를 늘리고, M보다 크면 start를 밀어내며 범위를 조절합니다. 이러한 과정은 데이터의 정합성을 유지하면서도 최소한의 연산으로 목적을 달성하게 합니다.

알고리즘 구현 시 단계별 가이드

  1. 포인터 초기화: start와 end 포인터를 배열의 첫 번째 인덱스(0)로 설정합니다.
  2. 조건부 이동: 현재 부분합이 타겟값보다 작으면 end를 1 증가시키고 새로운 요소를 합에 더합니다.
  3. 범위 축소: 현재 부분합이 타겟값보다 크거나 같으면 start를 1 증가시키고 기존 요소를 합에서 뺍니다.
  4. 결과 갱신: 부분합이 정확히 타겟값과 일치하는 순간을 카운트하거나 필요한 정보를 기록합니다.

이러한 방식은 슬라이딩 윈도우(Sliding Window)와 유사해 보이지만, 윈도우의 크기가 고정되지 않고 조건에 따라 신축적으로 변한다는 점에서 차별화됩니다. 실무에서는 로그 데이터 분석 중 특정 시간 동안의 이상 수치 합계를 구하거나, 네트워크 트래픽 패턴 중 특정 임계치를 넘는 최단 구간을 탐색하는 등 다양한 데이터 엔지니어링 영역에서 핵심 알고리즘으로 채택되고 있습니다. 특히 정렬된 배열에서의 두 수의 합 찾기는 면접 단골 문제로 등장할 만큼 그 효율성이 입증된 방식입니다.

3. 투 포인터 적용 시 주의사항과 경계값 처리(Edge Case)

투 포인터 기법은 성능상 강력하지만, 구현 시 세심한 인덱스 관리(Index Management)가 동반되지 않으면 런타임 에러(Runtime Error)를 유발하기 쉽습니다. 가장 흔한 실수는 포인터가 배열의 범위를 벗어나는 '인덱스 아웃 오브 바운즈(Index Out of Bounds)' 오류입니다. end 포인터가 배열의 끝인 N에 도달했을 때의 처리와 start 포인터가 end 포인터를 추월하지 않도록 제어하는 논리가 명확해야 합니다. 또한, 배열의 요소에 0이나 음수가 포함되어 있을 경우 구간합의 단조성이 깨지기 때문에 투 포인터만으로는 해결이 불가능할 수 있다는 점을 유의해야 합니다.

안정적인 구현을 위한 체크리스트

  • 루프 종료 조건: start가 N에 도달할 때까지 실행할 것인지, 혹은 특정 목표를 달성했을 때 즉시 탈출할 것인지 명확히 정의하십시오.
  • 자료형 선택: 부분합을 저장하는 변수는 데이터의 범위에 따라 오버플로우(Overflow)를 방지할 수 있는 long long(C++)이나 long(Java) 타입을 사용하십시오.
  • 중복 데이터 처리: 정렬된 배열에서 중복된 값이 존재할 경우, 포인터를 이동시킬 때 동일한 값을 건너뛰는 추가 로직이 필요한지 검토하십시오.

성능 최적화를 위해 투 포인터를 사용할 때는 항상 공간 복잡도(Space Complexity)와의 트레이드-오프를 고려해야 합니다. 투 포인터는 추가적인 메모리를 거의 사용하지 않는 O(1)의 공간 복잡도를 가지므로 메모리 효율이 매우 우수합니다. 하지만 문제의 성격에 따라 해시 맵(Hash Map)이나 누적 합(Prefix Sum) 배열을 병행하여 사용하면 시간 복잡도를 유지하면서도 로직의 단순함을 꾀할 수 있는 경우가 많습니다. 따라서 최적의 알고리즘 설계란 문제의 제약 조건을 완벽히 파악한 뒤 기술의 장단점을 적절히 조화시키는 과정이라 할 수 있습니다.

4. 실무 경험 및 개발자로서의 소회

작년 가을, 기존 프로젝트를 유지보수하던 중에 정말 식은땀 나는 경험을 했네요. 특정 사용자 그룹의 결제 내역 중 연속된 기간 내 목표 매출액을 달성한 최단기간을 뽑아내는 기능이었는데, 처음엔 별생각 없이 이중 for문을 돌렸거든요. 테스트 서버에선 잘 돌아가길래 배포했는데, 실제 운영 서버의 대용량 데이터를 만나니까 쿼리 실행 시간이 10초를 넘어가면서 타임아웃이 발생하더라고요. 당시에는 왜 안 되는지 몰라 정말 답답했거든요. 밤늦게까지 코드를 뜯어보다가 인천지역 개발자 모임에서 친해진 선배님이 "그거 투 포인터로 풀면 금방인데?"라고 힌트를 주셔서 바로 적용해 봤죠. 결과는 정말 놀라웠어요. 10초 걸리던 연산이 0.1초도 안 돼서 끝나는 걸 보고 알고리즘 효율성의 무서움을 제대로 느꼈답니다. 알고 보니 정말 사소한 루프 구조 하나 때문이었죠. 여러분은 저처럼 단순하게 코딩했다가 소중한 주말을 서버 복구에 반납하지 않고 즐겁게 보내셨으면 좋겠네요.

[오늘의 핵심 요약]
  • 원리: 두 개의 포인터를 조절하며 배열을 한 번만 탐색하여 O(N)의 성능 확보.
  • 유형: 양 끝에서 좁히는 방식(정렬 필요)과 같은 방향으로 이동하는 방식(구간합 활용).
  • 주의: 배열 범위를 벗어나는 인덱스 에러와 단조성이 깨지는 데이터 유무 확인 필수.

소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 tech-korea