-
Notifications
You must be signed in to change notification settings - Fork 6
5주차
1차원 배열이나 리스트를 다룰 때 두 개의 포인터(인덱스)를 조작하며 원하는 조건을 만족하는 결과를 찾는 알고리즘입니다.
- 두 개의 포인터(인덱스)를 조작하여 원하는 조건을 만족하는 결과를 탐색합니다.
- 완전 탐색(이중 for문)으로
O(N^2)의 시간이 걸리는 작업을O(N)으로 단축시킬 수 있다는 것이 가장 큰 핵심입니다. - 정렬된 배열에서의 탐색, 연속된 부분 배열(수열)의 처리와 깊게 연결되어 있습니다.
이름 그대로 두 개의 포인터(시작점과 끝점, 혹은 두 개의 이동점)를 사용하여 배열을 탐색하는 방식입니다.
- 보통 1차원 배열에서 두 개의 포인터를 조작합니다.
- 반복문 내에서 두 포인터가 각자의 조건에 따라 이동 방향과 속도를 결정합니다.
- 한 번 이동한 포인터는 뒤로 돌아가지 않으므로(역방향 이동 불가), 전체 배열을 한 번만 순회하게 되어 시간 복잡도
O(N)을 보장합니다. - 슬라이딩 윈도우와 자주 비교되며, 문제에 따라 혼용되어 쓰이기도 합니다.
1. 양방향 포인터
- 하나의 포인터는 배열의 처음(
Left), 다른 하나는 배열의 끝(Right)에서 시작합니다. - 주로 정렬된 배열에서 두 수의 합이나 차를 구할 때 사용됩니다.
- 조건에 따라
Left를 증가시키거나,Right를 감소시키며 서로 만날 때까지 가운데로 좁혀옵니다.
2. 동방향 포인터
- 두 포인터 모두 배열의 처음(
Start,End)에서 같은 방향으로 출발합니다. - 주로 연속된 부분 수열의 합이나 길이를 구할 때 사용됩니다.
- 조건에 따라
End를 전진시켜 구간을 넓히거나,Start를 전진시켜 구간을 좁히며 탐색합니다.
- 배열 안에서 두 요소의 합 또는 차를 구해야 하는데 입력 크기(N)가 100,000 이상이라서
O(N)또는O(N log N)으로 풀어야 한다면? ➡ 투 포인터 (양방향)
- 연속된 부분 배열이나 수열의 합이 특정 값이 되는 구간을 찾아야 한다면? ➡ 투 포인터 (동방향)
💡 참고: 양방향 투 포인터를 사용할 때, 배열이 정렬되어 있지 않다면 먼저 정렬
O(N log N)을 수행해야 합니다. 정렬 시간을 포함하더라도O(N^2)보다 훨씬 빠릅니다.
투 포인터 문제는 대부분 포인터의 이동 조건을 묻는 경우가 많으며, 다음과 같은 패턴이 등장하면 투 포인터 문제일 가능성이 높습니다.
1. 자주 나오는 문제 형태
- 정렬된 배열에서 두 수의 합이 Target이 되는 쌍 찾기
- 세 수의 합이 0이 되는 조합 찾기
- 가장 두 물의 양이 많은 용기 찾기 (수조 문제)
2. 접근 방법
- 배열을 오름차순으로 정렬합니다.
-
Left = 0,Right = 배열의 길이 - 1로 초기화합니다. - 두 값의 합이 Target보다 크면
Right를 왼쪽으로 이동시킵니다. (값을 줄임) - 두 값의 합이 Target보다 작으면
Left를 오른쪽으로 이동시킵니다. (값을 키움)
1. 자주 나오는 문제 형태
- 연속된 부분 수열의 합이 Target이 되는 경우의 수 찾기
- 합이 Target 이상이 되는 가장 짧은 연속 구간 길이 구하기
2. 접근 방법
-
Start = 0,End = 0으로 초기화합니다. - 현재 구간의 합이 Target보다 작거나 같으면
End를 오른쪽으로 이동하여 합을 증가시킵니다. - 현재 구간의 합이 Target보다 크거나 같으면 (또는 조건 만족 시)
Start를 오른쪽으로 이동하여 구간을 좁히고 합을 감소시킵니다.
자연수
n이 매개변수로 주어질 때, 연속된 자연수들로n을 표현하는 방법의 수를 반환하라! (예: 15는 1+2+3+4+5, 4+5+6, 7+8, 15로 총 4가지 방법으로 표현할 수 있다.)
연속된 수열의 합을 구하는 문제이므로, 두 포인터가 같은 방향으로 전진하는 동방향 투 포인터(또는 슬라이딩 윈도우)를 사용합니다.
- 두 포인터
start와end를 모두 1로 초기화합니다. -
start부터end까지의 합을sum이라고 할 때,sum과 목표값n을 비교하며 포인터를 이동시킵니다.-
sum == n: 조건을 만족하므로 경우의 수(정답)를 1 증가시킵니다. 새로운 구간을 탐색하기 위해sum에서start값을 빼고,start를 오른쪽으로 한 칸 이동시킵니다. (구간 축소) -
sum < n: 현재 합이 부족하므로 구간을 넓혀야 합니다. ➡end를 오른쪽으로 한 칸 이동시키고, 새로운end값을sum에 더합니다. -
sum > n: 현재 합이 넘치므로 구간을 좁혀야 합니다. ➡sum에서 현재start값을 빼고,start를 오른쪽으로 한 칸 이동시킵니다.
-
-
start가n이하일 때까지 위 과정을 반복합니다.
-
start = 1,end = 1➡ 합: 1 (15보다 작으므로end증가) -
start = 1,end = 5➡ 합: 15 (정답 도출! 카운트 증가 후start를 빼서 합을 줄이고start전진) -
start = 2,end = 5➡ 합: 14 (15보다 작으므로end증가) - ... 이런 식으로 꼬리를 물며 이동합니다!
⏱️ 시간 복잡도 :
O(N)> 이중 for문으로O(N^2)탐색을 하면 시간 초과가 날 수 있으나, 투 포인터는 각 포인터가 최대n번만 이동하므로 훨씬 빠릅니다.
function solution(n) {
let answer = 0;
let start = 1;
let end = 1;
let sum = 1; // start부터 end까지의 합
while (start <= n) {
if (sum === n) {
answer++; // 정답 카운트
sum -= start; // 현재 start 값을 빼고
start++; // start를 오른쪽으로 이동
} else if (sum < n) {
end++; // end를 오른쪽으로 이동하고
sum += end; // 새로운 end 값을 더함
} else { // sum > n
sum -= start; // 현재 start 값을 빼고
start++; // start를 오른쪽으로 이동
}
}
return answer;
}코드 해설:
- 시작점
start와 끝점end를 1로 두고, 초기 합(sum)도 1로 설정합니다. - 매번 반복문을 돌면서 1부터
n까지 일일이 합을 다시 구하지 않고, 포인터가 이동할 때마다 양 끝 값만 더하거나 빼주어 연산을 최소화합니다.
def solution(n):
answer = 0
start = 1
end = 1
total_sum = 1
while start <= n:
if total_sum == n:
answer += 1
total_sum -= start
start += 1
elif total_sum < n:
end += 1
total_sum += end
else:
total_sum -= start
start += 1
return answer코드 해설:
- 파이썬의 내장 함수
sum()을 매번 호출하면 슬라이싱하는 데O(N)이 걸려 전체O(N^2)이 되므로,total_sum이라는 정수 변수에 값을 직접 더하고 빼는 방식이 핵심입니다.
class Solution {
public int solution(int n) {
int answer = 0;
int start = 1;
int end = 1;
int sum = 1;
while (start <= n) {
if (sum == n) {
answer++;
sum -= start;
start++;
} else if (sum < n) {
end++;
sum += end;
} else {
sum -= start;
start++;
}
}
return answer;
}
}코드 해설:
- 이중 반복문(for문 두 개)을 사용했을 때 발생할 수 있는 불필요한 중복 연산을,
sum변수 하나를 유지하며 더하고 빼는 방식으로O(N)의 시간 복잡도로 최적화했습니다.
양끝에서 좁혀오는 투 포인터 로직은 '배열이 오름차순으로 정렬되어 있다는 가정' 하에 성립합니다. 정렬되지 않은 배열에서 크기 비교를 통해 포인터를 이동시키면 전혀 엉뚱한 결과를 낳게 됩니다. 입력된 배열이 정렬되어 있는지 반드시 확인하고, 안 되어 있다면 먼저 정렬(sort())을 수행해야 합니다.
while (left <= right)를 써야 할지, while (left < right)를 써야 할지 헷갈려 하는 경우가 많습니다.
- 서로 다른 두 원소를 골라야 하는 문제에서는
left < right가 맞습니다. (<=를 쓰면 같은 원소를 두 번 더하는 꼴이 될 수 있음) - 문제의 요구사항에 따라 하나의 원소를 중복해서 선택해도 되는 상황이라면 범위를 다르게 설정해야 하므로 주의가 필요합니다.
start와 end가 같은 방향으로 이동하는 문제에서, end가 배열의 끝을 넘어갔음에도 계속 인덱스에 접근하려고 하면 에러(Index Out Of Bounds)가 발생합니다. 반복문 안에서 포인터가 배열의 길이(N)를 초과하지 않도록 안전하게 예외 처리를 해두는 습관이 필요합니다.
Input Size & Algorithm Complexity
5주차 - Two Pointers / Sliding Window
13주차 - Simulation / Implementation
백현빈 → 강민주 → 조수빈 → 임현빈 → 전병훈 → 이건희
(이후 동일한 순서로 반복)