-
합이 0에 가장 가까운 구간 / 알고리즘 문제해결전략 2권ALGOSPOT 2020. 2. 19. 17:40728x90
이번 포스팅은 알고리즘 문제해결전략 2권의 601페이지에 나오는 '합이 0에 가장 가까운 구간' 예제 입니다.
자바 코드 : https://github.com/skysign/WSAPT/blob/master/ALGOSPOT/%ED%95%A9%EC%9D%B4%200%EC%97%90%20%EA%B0%80%EC%9E%A5%20%EA%B0%80%EA%B9%8C%EC%9A%B4%20%EA%B5%AC%EA%B0%84/src/Main.java우선 제목에서 가장 '가까운 구간' 이라고 해서, 책의 내용이 구간을 찾는 풀이로 오해 하실 수 있습니다. 책에 있는 내용은, 0에 가장 가까운 구간합을 찾는 것입니다. 여기서는 합이 0에 가장 가까운 구간도 함께 찾아 보겠습니다.
이 문제는 구간합(prefixsum)을 활용하는 방법을 배울 수 있는 문제입니다.
O(NlogN)에 관한 책의 설명을 좀더 보충해서 설명하자면, prefix sum을 구하고 정렬을 한뒤, 0에 가까운 구간을 찾으면, O(NLogN)이 됩니다.
- O(N)은 구간합을 계산할 때, 걸리는 시간 복잡도 이구요,
- O(NlogN)은 prefixSum을 정렬하는대 걸리는 시간 복잡도 이구요,
- O(N)은 정렬된 prefixSum에서, 0에 가장 가까운 구간합을 찾을 때, 걸리는 시간 복잡도 입니다.
- 따라서, O(N)은 없어지고 보다 큰 시간 복잡도인, O(NlogN)만 남습니다.
보다 설명할 부분은 prefix sum을 정렬을 하면, 어떻게 O(N)만에 0에 가장 가까운 구간합을 얻을 수 있느냐? 입니다.
구간합이 0에 가장 가까우려면, 2개의 임의의 구간합이 있다고 할 때, 둘의 차이가 가장 작을 수록, 0에 가까울수 있습니다.구간합과는 아무런 관계없이, 정렬을 하게 되면 얻을 수 있는 특성이 있습니다. 즉, 어떤 수의 집합을, 각 수의 크기로 정렬을 하게 되면, 서로 인접한 숫자는, 그 집합의 어떤 숫자와 비교하더라도, 인접한 두수의 차이가 가장 적게 됩니다.
이 특성을 활용해서, min(pSum[i] - pSum[i-1])이 최소가 되는 값을 찾으면, 합이 0에 가장 가까운 구간합을 찾을 수 있습니다.아래 구현에서는 추가적으로 실제로, 구간도 찾아 보았습니다.
Pair의 key 부분에는 배열의 index를, value부분에는 index까지 구간합을 저장하고, 샘플입력을 아래와 같이 했을 때, pairs가 value기준으로 소팅된 모습은 아래와 같습니다.샘플입력
10 -14 7 2 3 -8 4 -6 8 9 11
pairs의 소팅 결과
위의 캡처의 선택된 부분, 즉, pSum[5] - pSum[1] 값이, 0에 가장 가까운 구간합이므로, 구간은 인덱스로 2~5 까지 입니다.
코드 주요 부분
public class Main { int N; int[] dt; int[] pSum; public void solve() throws IOException { N = sc.nextInt(); dt = new int[N]; pSum = new int[N]; Pair[] pairs = new Pair[N]; dt[0] = sc.nextInt(); pSum[0] = dt[0]; pairs[0] = new Pair<Integer, Integer>(0, pSum[0]); for(int i=1; i<N; ++i) { dt[i] = sc.nextInt(); pSum[i] = dt[i] + pSum[i-1]; // 구간을 구하기 위해서, Pair에 key에 배열의 인덱스를, value에 prefix sum의 값을 저장한다. pairs[i] = new Pair<Integer, Integer>(i, pSum[i]); } Arrays.sort(pSum); // pari의 value기준으로 소팅하고 Arrays.sort(pairs); // 합이 0에 가장 가까운 구간의 합을 구하는 방법 int r = Math.abs(pSum[0]); for(int i=1; i<N; ++i) { r = Math.min(r, Math.abs(pSum[i] - pSum[i-1])); } println(r); println(); // 합이 0에 가장 가까운, 구간을 구하는 방법 r = Math.abs((Integer)pairs[0].getValue()); int k1 = -1; int k2 = -1; for(int i=1; i<N; ++i) { int t = r; t = Math.min(t, Math.abs((Integer)pairs[i].getValue() - (Integer)pairs[i-1].getValue())); // 보다 구간의 합이 0에 가까운 구간을 찾았으면, // 구간을 k1과 k2에 저장함 if(t != r) { k1 = (Integer)pairs[i].getKey(); k2 = (Integer)pairs[i-1].getKey(); r = t; if(k1<k2) k1++; else k2++; } } println(r); println(k1 + "~" + k2); } }
728x90'ALGOSPOT' 카테고리의 다른 글
고대어 사전 / ALGOSPOT (0) 2020.03.26 TRAVERSAL 트리 순회 순서 변경 / ALGOSPOT (0) 2020.03.02 PACKING 여행 짐 싸기 / ALGOSPOT (0) 2020.02.09 TRIPATHCNT 삼각형 위의 최대 경로 수 세기 / ALGOSPOT (0) 2020.02.07 LIS Longest Increasing Sequence / ALGOSPOT (0) 2020.02.05