Tech/Algorithm
[BOJ] 백준 1806 부분합 c++ (투포인터)
0m1n
2023. 1. 9. 14:27
728x90
반응형
문제 출처 : https://www.acmicpc.net/problem/1806
1806번: 부분합
첫째 줄에 N (10 ≤ N < 100,000)과 S (0 < S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.
www.acmicpc.net

풀이
연속된 수의 부분합 중 S 이상이 되는 것 중, 가장 짧은 것의 길이를 구하는 문제이다.
단순하게 합을 구한다고 해서 정렬이나 priority_queue를 쓰면 안된다. 연속된 수의 부분합이기 때문이다.
그렇다고 브루트포스로 구현하면 시간초과가 발생한다. 어떻게 해야 할까?
바로 투포인터 알고리즘을 사용하면 된다.
투포인터 알고리즘
이름 그대로 배열을 이동하는 포인터가 두개라고 생각하면 된다.
두 포인터를 이동시키며 그 사이의 합을 구할 것이다. (연속된 수의 부분합)
이 합이 S보다 크고 가장 짧은 것이 정답이다.
- 처음 두 포인터 start, end를 선언하고 가장 첫 부분(1)에 위치시킨다.
- 두 포인터가 둘다 1에 있으므로 초기 합(sum)은 arr[1]이다.
- 반복문 범위는 start가 end를 넘지 않고, end가 N를 넘지 않게 설정한다.
- 합이 S보다 크면 그동안의 최소치와 비교해 ans에 저장한다.
- 만약 합이 S보다 작으면 두 포인터의 범위를 넓혀야 한다. 따라서 end 포인터를 1증가시키고 증가시킨 부분을 더한다.
- 합이 S보다 큰 경우 최소치를 검사하기 위해 start 포인터를 1 증가시키고 좁힌 부분을 뺀다.
이렇게 범위를 이동시키면서 정답을 구할 수 있다.
// arr은 수가 담겨있는 배열
int start = 1, end = 1;
int sum = arr[1];
int ans = INT_MAX;
while(start <= end && end <= N){
if(sum >= S) ans = min(ans, end-start+1);
if(sum < S){
end++;
sum += arr[end];
}else{
sum -= arr[start];
start++;
}
}
코드
#include <bits/stdc++.h>
using namespace std;
int arr[100000+1];
int main(void) {
ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
int N, S;
cin >> N >> S;
for(int i = 1; i <= N; i++)
cin >> arr[i];
int start = 1, end = 1;
int sum = arr[1];
int ans = INT_MAX;
while(start <= end && end <= N){
if(sum >= S) ans = min(ans, end-start+1);
if(sum < S){
end++;
sum += arr[end];
}else{
sum -= arr[start];
start++;
}
}
if(ans == INT_MAX) cout << 0 << '\n';
else cout << ans << '\n';
return 0;
}
728x90
반응형