처음에 DP라고 생각하고 풀어서 맞췄다.
알고보니 투 포인터 문제였다.
투 포인터가 무엇인지 몰랐는데 이 문제가 대표적인 투 포인터 문제라고 한다.
투 포인터란?
1차원 배열에서 두 개의 포인터를 가지고 조작하면서 원하는 값을 얻는 알고리즘이다. 이 때 배열의 모든 원소는 자연수여야만 투 포인터 알고리즘이 가능하다. |
문제 접근방식
처음 내 풀이는 DP로 풀었다.
시간 제한을 보아도 DP밖에 방법이 떠오르지 않았다.
동적 계획법 Bottom Up 방식을 사용해서 문제를 풀었다.
그리고 각 자리수가 증가할 때마다 해당 다음 번째 수에 있는 값을 더해 나간다고 생각하고 풀었다.
예시 입력을 예로들면, dp가 결과값을 담는 배열이고 num이 각 값을 담는 배열이라 할 때
N = 10, M = 5이고 원소가 1 2 3 4 2 5 3 1 1 2이면,
- 처음 자기 자신만 사용하는 경우 : dp[1] = 1, dp[2] = 2 + ... + dp[10] = 2가 된다.
- 자기 자신에서 다음번째까지 사용하는 경우 : dp[1] = dp[1] + num[2], dp[2] = dp[2] + num[3] + ... + dp[9] = dp[9] + num[10]
이런 식으로 dp 값을 업데이트 해 나가면서, 만일 값이 찾는 값과 같은 값이 나온다면 결과에 하나를 더해준다.
막상 풀어보니, 시간이 꽤 오래걸리는 것 같아서 다른 방식이 있는지, 잘못 푼건지 알고리즘 분류를 확인해보니
투 포인터 알고리즘을 사용하는 것이었다.
그냥 넘어갈 수도 있었지만, 한 번 푼 문제는 다시 풀 기회가 없을 것 같아서 투 포인터 알고리즘을 써보기로 했다.
문제풀이
#include <iostream> using namespace std; int numCnt, findNum; int num[10001]; int dp[10001]; int result; int main() { ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL); cin >> numCnt >> findNum; int input; for (int i = 1; i <= numCnt; i++) { cin >> input; num[i] = input; dp[i] = input; if (num[i] == findNum) { result++; } } for (int i = 1; i <= numCnt; i++) { for (int j = 1; j <= numCnt; j++) { if (j + i > numCnt) { break; } dp[j] = dp[j] + num[j + i]; if (dp[j] == findNum) { result++; } } } cout << result; return 0; } |
우선 DP로 짠 코드는 위와 같다.
DP 문제는 아니므로 설명은 접근방식에서 했던 것으로 대체하고 DP 풀이는 생략하겠다.
이 문제에서 투 포인터에 대해 어떻게 적용되는지 살펴보자면,
투 포인터는 시작, 끝 그리고 부분 합이라는 개념이 적용된다.
시작과 끝은 처음에 항상 첫번째 인덱스(보통 0번째지만 나는 첫번째로 사용한다.)를 나타낸다.
다음 테스트 케이스로 예시를 들겠다.
10 5 1 2 3 4 2 5 3 1 1 2 |
- start = 1, end = 1, sum = 0이다. sum < 5이므로, end 포인터를 하나 증가시키며 첫번째 idx에 있는 원소를 sum에 더해준다. sum = 0 -> 1이 된다.
- start = 1, end = 2, sum = 1이다. sum < 5이므로, end 포인터를 하나 증가시키며 두번째 idx에 있는 원소를 sum에 더해준다. sum = 1 -> 3이 된다.
- start = 1, end = 3, sum = 3이다. sum < 5이므로, end 포인터를 하나 증가시키며 세번째 idx에 있는 원소를 sum에 더해준다. sum = 3 -> 6이 된다.
- start = 1, end = 4, sum = 6이다. sum >= 5이므로, start 포인터를 하나 증가시키며 첫번째 idx에 있는 원소를 sum에서 빼준다. sum = 6 -> 5가 된다.
- start = 2, end = 4, sum = 5이다. sum == 5이므로, 결과값을 하나 증가시키고 start 포인터를 하나 증가시키며 두번째 idx에 있는 원소를 sum에서 빼준다. sum = 5 -> 3이 된다.
- ...
다음과 같은 방식으로 진행이 되며, end가 원소의 개수보다 커질때 반복문을 종료시켜주면 된다.
코드를 살펴보자.
원소의 개수 numCnt와 찾는 수 findNum이 있다.
그리고 원소를 담을 배열 arr와 결과값을 출력할 result변수가 있다.
원소의 개수와 찾는 수를 입력받고 각 원소를 입력받는다.
이 부분이 투 포인터를 쓰는 메인함수의 아랫부분이다.
시작 인덱스와 끝 인덱스를 1로 초기화 해준다. (0번째 배열부터 쓰는 사람은 0으로 초기화 해주어야 한다.)
그리고 반복분의 조건에 startIdx <= endIdx라고 되어있는데, 사실 이건 당연한 것으로, while(true)와 같은 의미이다.
이유는 sum이 0이나 findNum보다 작아지게 되면 endIdx를 증가시키게 되므로, startIdx는 당연히 endIdx보단 작아지게 된다.
투 포인터를 구현할 때에는 반복문의 순서가 상당히 중요하다.
- sum이 findNum보다 크거나 같다면 sum에서 start가 가리키는 원소를 빼주고 start에 1을 더해준다.
- sum이 findNum보다 작다면, end가 가리키는 원소를 더해주고 end에 1을 더해준다. 이 때, end가 증가되기 전에 이미 end가 원소의 개수보다 커져있다면 반복문을 종료시킨다. end가 원소 개수보다 이미 커져있다는 뜻은 뒤에 원소가 더이상 존재하지 않는다는 뜻이므로 확인할 필요가 없다.
- 현재 sum이 찾는 수와 같다면 결과값에 1을 더해준다.
처음에 DP로 맞추고, 투 포인터 알고리즘을 공부하면서 코드를 제출하면서 공부했더니 너무 많이 틀려버렸다.
새로 알게된 알고리즘으로, 잊지말고 머리속에 담아둬야겠다.
코드는 위에서 확인 가능하다.
'알고리즘 > Baekjoon' 카테고리의 다른 글
[C++] 백준 7562번 나이트의 이동 (0) | 2021.08.17 |
---|---|
[C++] 백준 2583번 영역 구하기 (0) | 2021.08.17 |
[C++] 백준 14503번 로봇 청소기 (0) | 2021.08.15 |
[C++] 백준 10026번 적록색약 (0) | 2021.08.15 |
[C++] 백준 15686번 치킨 배달 (0) | 2021.08.15 |