[ 1 ] 기본내용
"투 포인터"는 1차원 배열에서 각기 다른 원소를 가리키는 2개의 포인터를 활용하여 원하는 값을 얻는 알고리즘이다. 이때, 포인터는 C/C++에서의 포인터가 아닌 배열에서의 "인덱스"를 의미한다.
2개의 포인터를 적절히 이동시키며 시간 복잡도를 최적화할 수 있는 것이 투 포인터 알고리즘의 핵심이다.
투 포인터가 자주 활용되는 문제로는
(1) target 값을 미리 설정하고, 특정 배열 내에 target 값과 같은 - 유사한 부분 합이 몇 개 존재하는지 계산하는 문제
(2) 배열 내 target = arr[i] + arr[j] 를 만족하는 (i, j) 쌍이 몇 개 존재하는지 계산하는 문제
등이 있다.
[ 2 ] 부분 합에서의 투 포인터
해당 내용은 대표 예제인 백준 2003번을 통해 살펴보도록 하자.
문제 출처: https://www.acmicpc.net/problem/2003
2003번: 수들의 합 2
첫째 줄에 N(1 ≤ N ≤ 10,000), M(1 ≤ M ≤ 300,000,000)이 주어진다. 다음 줄에는 A[1], A[2], …, A[N]이 공백으로 분리되어 주어진다. 각각의 A[x]는 30,000을 넘지 않는 자연수이다.
www.acmicpc.net
해당 문제는 구간 합(Prefix Sum)을 구하여 해결할 수도 있지만, 그럴 경우 이중 for문을 사용해야 하므로 시간 초과가 발생한다. 이때 투 포인터를 활용하면 시간복잡도를 최적화할 수 있다.
< 투 포인터의 기본 의미 >
- 두 개의 포인터 start와 end는 부분 배열의 시작과 끝을 의미한다.
- 두 개의 포인터 start와 end는 둘 다 0에서부터 시작하며, 항상 start <= end를 만족해야 한다.
S를 start와 end 사이의 부분 합, M을 target 값이라 가정하자.
< 알고리즘 실행 과정 >
- start와 end가 N(배열의 크기)에 도달할 때 알고리즘이 끝난다.
- S > M 인 경우 or end가 이미 N에 도달한 경우
▶ 범위를 좁히기 위해 start++ 를 한다.
- 그렇지 않은 경우
▶ 범위를 넓히기 위해 end++를 한다.
- S == M 인 경우
▶ 우리가 찾는 값이므로 cnt++를 한다.
다음 그림을 통해 위 과정을 이해해보자.
파란 포인터가 start, 빨간 포인터가 end이다.
(1) start = end = 0. start = end일 경우 부분 배열의 합은 0이다. S < M 이므로 end++를 한다.
(2) 아직 S < M 이므로 end++
(3) 아직 S < M 이므로 end ++
(4) 이제 S > M 이므로 start++ 를 한다.
(5) S = M 이므로 cnt++를 한다.
(6) S > M 이므로 start++
(7) S > M 이므로 start++
(8) S < M 이므로 end++
(9) S > M 이므로 start++
(10) S < M 이므로 end++
(11) S > M 이므로 start++
(12) S = M 이므로 cnt++를 한다.
위와 같은 과정을 start와 end가 N(배열의 크기)에 다다를때까지 반복하면 cnt = 3이 나온다.
소스코드는 다음과 같다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int N = Integer.parseInt(st.nextToken());
int M = Integer.parseInt(st.nextToken());
int[] arr = new int[N];
st = new StringTokenizer(br.readLine());
for(int i = 0; i < N; i++)
{
arr[i] = Integer.parseInt(st.nextToken());
}
int start = 0;
int end = 0;
int cnt = 0;
int sum = 0;
while(!(start == N && end == N))
{
if(sum > M || end == N)
{
sum -= arr[start++];
}
else
{
sum += arr[end++];
}
if(sum == M)
{
cnt++;
}
}
System.out.println(cnt);
}
}
투 포인터 알고리즘은 start와 end가 0에서 N에 다다르면 끝이 난다는 점에서 시간복잡도가 O(N)이다.
[ 3 ] 배열 내 target = arr[i] + arr[j] 찾기
위의 부분 합뿐만 아니라, 두 개의 원소를 활용하여 target을 찾을 때에도 투 포인터를 활용할 수 있다.
과정은 다음과 같다.
<기본 설정>
- 배열이 정렬되어 있어야 한다.
- start = 0, end = N-1에서 시작한다.
<알고리즘 실행 과정>
- arr[start] + arr[end] > target
▶ end-- 를 한다. 정렬되어 있으므로 큰 수를 줄여 target에 가까워지도록 하기 위함이다.
- arr[start] + arr[end] < target
▶ start++를 한다.
- arr[start] + arr[end] = target
▶ cnt++를 한 후 start++, end--를 한다.
위 과정을 start < end 를 만족하는 동안 반복한다.
문제 출처: https://www.acmicpc.net/problem/1940
1940번: 주몽
첫째 줄에는 재료의 개수 N(1 ≤ N ≤ 15,000)이 주어진다. 그리고 두 번째 줄에는 갑옷을 만드는데 필요한 수 M(1 ≤ M ≤ 10,000,000) 주어진다. 그리고 마지막으로 셋째 줄에는 N개의 재료들이 가진 고
www.acmicpc.net
이 문제를 통해 본 알고리즘을 연습할 수 있다.
'CS > 알고리즘' 카테고리의 다른 글
[알고리즘/Java] 유니온 파인드(Union-Find) (2) | 2023.04.28 |
---|---|
[알고리즘/Java] 슬라이딩 윈도우(Sliding Window) (0) | 2022.07.27 |
[알고리즘/Java] 구간 합(Prefix Sum)과 관련 예제(BOJ-11659) (0) | 2022.07.20 |