-
핵심 원리:
- right는 0 → N까지 한 번만 증가
- left는 필요할 때만 증가 (역방향 없음)
→ 전체 O(N)
💎 투 포인터 공통 템플릿 (슬라이딩 윈도우 기본형)
int left = 0;
long sum = 0; // 누적 상태 (합이든, 카운트든 조건 체크용)
for (int right = 0; right < N; right++) {
// 1) right 확장
sum += A[right];
// 2) 조건을 위반하면 left 조정 (왼쪽을 줄여서 다시 만족시키기)
while (sum > K) { // 문제에 따라 조건 변경
sum -= A[left];
left++;
}
// 3) 여기 왔을 때는 sum이 조건을 만족
// → 문제 유형별로 다른 처리
// 예: count += (right - left + 1);
// 예: ans = Math.min(ans, right-left+1);
// 예: ans = Math.max(ans, right-left+1);
}🚀 1. 투 포인터(Two Pointers)란?
두 개의 인덱스(왼쪽, 오른쪽)를 움직이면서
O(N²) 문제를 O(N) 또는 O(N log N)으로 줄이는 기법즉,
- i = 시작 지점(left pointer)
- j = 끝 지점(right pointer)
이 두 변수를 조절하면서
모든 (i,j) 쌍을 “정확히 한 번씩” 확인하는 구조를 만드는 알고리즘이야.
🚨 2. 왜 필요한가?
예를 들어, 다음 문제가 있다고 해봐.
“연속 부분 배열 중 합이 K 이하인 모든 구간의 수 구하기”
이걸 단순히 이중 for문으로 풀면:
for (i = 0..N) for (j = i..N)→ O(N²)
→ N = 200,000이면 절대 못 풀어.그래서 나온 아이디어가 투 포인터야.
🔥 3. 투 포인터의 핵심 아이디어 3줄 요약
✔ (1) 오른쪽 포인터를 0부터 N까지 한 번만 증가시킨다
→ “확장”
✔ (2) 조건을 만족하지 않으면 왼쪽 포인터를 증가시켜 조절한다
→ “축소”
✔ (3) 두 포인터 각각 최대 N번씩만 이동
→ 전체 시간복잡도 O(N)
즉,
중첩 for문처럼 모든 조합을 다 도는 게 아니라,
포인터가 뒤로만 가므로 절대 뒤로 가지 않음 (no backtracking).
🌈 4. 투 포인터가 가능한 문제 조건
투 포인터는 이 조건이 있을 때만 가능해:
✔ 배열 원소가 “음이 아닌 경우”
(≥ 0)
왜?
- 합이 커지면 right를 줄여도 다시 작아지지 않음
- left를 당기면 합이 반드시 감소함
- 단조(monotonic)한 특성이 필요
즉,
왼쪽으로 이동하면 sum 감소,
오른쪽으로 이동하면 sum 증가.그래서 left/right 둘 다 뒤로 돌아갈 필요가 없음.
🧠 5. 한 번에 이해하는 예시
배열:
A = [10, 1, 6, 5, 2, 1] K = 15우리는:
합이 K 이하인 구간 (i,j)의 개수를 구하고 싶다.
👉 투 포인터 흐름 (직관적 설명)
step1: right=0
sum=10 → OK
구간 개수 += (0 - 0 + 1) = 1step2: right=1
sum = 10 + 1 = 11 → OK
구간 2개:- (0,1)
- (1,1)
count += 2
step3: right=2
sum = 11 + 6 = 17 → K 초과
left 증가 → left=1
sum = 17 - 10 = 7 → OK
구간 2개:- (1,2)
- (2,2)
count += 2
step4: right=3
sum = 7 + 5 = 12 OK
구간 3개
- (1,3), (2,3), (3,3)
count += 3
이런 식으로 left/right 둘 다 앞으로만 움직이면서 모든 구간 세기 가능
- [0 ~ right+1]은 당연히 예전에 K를 넘던 데다가 더 큰 수를 더했으니까,
또 K 넘을 가능성이 더 큼 - 그러니까 다시 left=0으로 돌아가서 볼 필요가 없음
🧩 6. 투 포인터 표준 코드 (암기용 sum<=K)
long count = 0;
int left = 0;
long sum = 0;
for (int right = 0; right < N; right++) {
sum += A[right];
// 조건 위반 → left 증가
while (sum > K && left <= right) {
sum -= A[left];
left++;
}
// 여기서 sum <= K를 만족
// right를 끝으로 하는 유효 구간 수:
count += (right - left + 1);
}✔ 복잡도
- right: 0→N 까지 N번
- left: 한 번 증가하면 다시 줄지 않음 → 최대 N번
- 따라서 O(N)
🎯 7. 투 포인터가 쓰이는 문제 패턴
📌 (1) 합 조건을 만족하는 구간 찾기
- 부분합 ≤ K
- 부분합 = K
- 최소 길이
- 최대 길이
📌 (2) 길이가 최소/최대인 구간 찾기
📌 (3) 두 포인터 정렬 배열에서 값 찾기
- A[left] + A[right] == target
- 삼각형 조건
- 3sum / 4sum
📌 (4) 문자열에서 길이 가지는 윈도우
- "슬라이딩 윈도우" 문제들이 사실상 투 포인터 변형
💎 투 포인터 공통 템플릿 (슬라이딩 윈도우 기본형)
int left = 0;
long sum = 0; // 누적 상태 (합이든, 카운트든 조건 체크용)
for (int right = 0; right < N; right++) {
// 1) right 확장
sum += A[right];
// 2) 조건을 위반하면 left 조정 (왼쪽을 줄여서 다시 만족시키기)
while (sum > K) { // 문제에 따라 조건 변경
sum -= A[left];
left++;
}
// 3) 여기 왔을 때는 sum이 조건을 만족
// → 문제 유형별로 다른 처리
// 예: count += (right - left + 1);
// 예: ans = Math.min(ans, right-left+1);
// 예: ans = Math.max(ans, right-left+1);
}핵심 원리:
- right는 0 → N까지 한 번만 증가
- left는 필요할 때만 증가 (역방향 없음)
→ 전체 O(N)
🔥 단계별 설명
1) right 확장 (윈도우 키우기)
sum += A[right];지금 보고 있는 요소를 윈도우에 포함함.
2) 조건 위반 시 left를 이동 (윈도우 줄이기)
while (sum > K) { sum -= A[left]; left++; }문제 유형에 따라 조건은 달라짐:
- sum > K
- 길이가 초과됨
- 포함해야 할 문자가 없음
- distinct 개수가 많음
- 특정 문자의 빈도가 넘침
등등.
3) 조건을 만족하는 상태에서 문제별 처리 로직 적용
- 모든 구간 개수 세기
→ count += (right-left+1);
-최소 길이
→ minLen = Math.min(minLen, right-left+1);- 최대 길이
→ maxLen = Math.max(maxLen, right-left+1); - 특정 문자 개수 유지
→ if (sum == K) count++; - 필요한 문자 모두 포함됨
→ 정답 갱신, left를 당겨 최소화 시도
🎯 이 템플릿에서 바뀌는 부분은 딱 2곳
① 조건 위반 기준
(ex: sum > K, 길이 초과, distinct > 2 등)
② 조건 만족 상태에서 처리하는 로직
(ex: count += …, minLen 갱신, maxLen 갱신)
둘만 바뀌면 모든 투포인터/슬라이딩 윈도우 문제를 해결할 수 있다.
📌 문제 유형별 템플릿 적용 예시 요약
① 부분합 ≤ K인 모든 구간 개수
while (sum > K) sum -= A[left++]; count += (right - left + 1);
② sum ≥ K인 “최소 길이” 구하기
while (sum >= K) { minLen = Math.min(minLen, right-left+1); sum -= A[left++]; }
③ sum ≤ K인 “최대 길이” 구하기
while (sum > K) sum -= A[left++]; maxLen = Math.max(maxLen, right-left+1);
④ 문자열에서 조건 만족하는 최소 윈도우
// 오른쪽으로 확장 (문자 카운트 늘리고 조건 달성) while (조건 달성됨) { minLen = Math.min(minLen, right-left+1); // left 줄이기 시도 left++; }