Post

투 포인터(Two Pointer)

투 포인터 알고리즘

투 포인터 알고리즘은 배열에서 각 원소를 가리키는 두 개의 포인터를 조작하여 $O(N)$에 문제를 해결하는 알고리즘이다.

일반적으로 투 포인터는 두 가지 종류가 있다. 두 포인터가 양 끝에서 시작하는 경우와 한 점(일반적으로 시작점)에서 시작하는 경우다.


한 점에서 시작하는 경우

2003번: 수들의 합2는 배열에서 연속된 원소의 합이 M이 되는 경우의 수를 구하는 문제이다.

이 문제를 naive하게 풀면 가능한 모든 쌍에 대해 합을 구해야 하므로 시간 복잡도는 $O(N^2)$이다. 하지만 투 포인터 알고리즘을 사용하면 $O(N)$에 풀 수 있다.

배열의 원소 두 개를 가리키는 포인터를 각각 left, right (left $\leq$ right) 라고 정의하고, left부터 right까지 원소의 합을 sum(left, right) 라고 정의하자. left, right 모두 배열의 첫 번째 원소를 가리킨 상태로 시작한다. 그리고 다음 두 가지 경우에 따라 알고리즘을 진행하면 된다.

첫 번째는 sum(left, right) $\ge$ M인 경우이다. 이 경우 left를 오른쪽으로 한 칸 이동한다. sum(left - 1, right)와 sum(left, right + 1)은 sum(left, right)보다 크기 때문에 left를 왼쪽으로 움직이거나 right를 오른쪽으로 움직일 이유가 없다. 또한 sum(left, right - 1)은 이전 단계에서 이미 구했기 때문에 right를 왼쪽으로 움직일 이유도 없다. 하지만 sum(left + 1, right)는 sum(left, right)보다 작기 때문에 M과 같거나 작아질 가능성이 있다. 다시 말해서 해가 될 가능성이 있다는 뜻이다.

두 번째는 sum(left, right) < M인 경우이다. 이 경우에는 right를 오른쪽으로 한 칸 이동한다. 이유는 위와 같다.

로직에 맞게 적절히 구현하면 AC를 받을 수 있다.


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <bits/stdc++.h>
#define FastIO ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
#define ll long long
using namespace std;

int N, M, ans;
vector<int> v;

void Output() {
    cout << ans;
}


void Solve() {
    int left = 0, right = 0, sum = v[0];

    while(true) {
        if(sum == M) {
            ans++;
        }

        if((left == right) || (sum < M)) {
            if(right == N - 1) {
                break;
            }

            sum += v[++right];
        }
        else {
            sum -= v[left++];
        }
    }
}


void Input() {
    cin >> N >> M;

    for(int i = 0; i < N; i++) {
        int num;
        cin >> num;

        v.push_back(num);
    }
}


int main() {
    FastIO;

    Input();
    Solve();
    Output();
}

양 끝에서 시작하는 경우

2470번: 두 용액은 N개의 원소가 주어졌을 때, 합이 0에 가장 가까운 두 원소를 찾는 문제이다.

이 문제도 위 문제와 동일하게 naive하게 풀면 $O(N^2)$이지만, 투 포인터 알고리즘을 사용하면 $O(N)$에 풀 수 있다.

left는 배열의 첫 번째 원소를, right는 마지막 원소를 가리킨 상태로 시작한다. sum(left, right)는 left와 right의 합이라고 정의하자. 이제 배열을 정렬하고 문제를 풀면 된다.

sum(left, right) $\ge$ 0인 경우에는 right를 왼쪽으로 옮기고, sum(left, right) $\lt$ 0인 경우에는 left를 오른쪽으로 옮겨주면 된다.

투 포인터 알고리즘을 모든 문제에서 사용할 수 있는 것은 아니다. 위의 문제들처럼 모든 sum(left, right)쌍을 다 확인하지 않아도 되는 경우에만 사용할 수 있다.

이 문제에서도 {left, k}에서 left가 오른쪽으로 이동하는 경우, 혹은 {k, right}에서 right가 왼쪽으로 이동하는 경우에 해당 left, right가 포함되는 조합은 다시 살펴볼 필요가 없다.


코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <bits/stdc++.h>
#define FastIO ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
#define ll long long
using namespace std;

int N;
vector<int> v;
int ans[2];

void Output() {
    cout << ans[0] << " " << ans[1];
}


void Solve() {
    sort(v.begin(), v.end());

    int left = 0, right = v.size() - 1, diff = 2000000001;

    while(left < right) {
        int sum = v[right] + v[left];

        if(abs(sum) < abs(diff)) {
            diff = abs(sum);
            ans[0] = v[left];
            ans[1] = v[right];
        }

        if(sum > 0) {
            right--;
        }
        else {
            left++;
        }
    }
}


void Input() {
    cin >> N;

    for(int i = 0; i < N; i++) {
        int num;
        cin >> num;

        v.push_back(num);
    }
}


int main() {
    FastIO;

    Input();
    Solve();
    Output();
}

This post is licensed under CC BY 4.0 by the author.