누적합과 차분 배열
고정된 배열에서 구간 합을 O(1)에 구하는 누적합(Prefix Sum)과, 여러 구간에 값을 일괄로 더하는 작업을 O(1)에 처리하는 차분 배열(Difference Array) 기법입니다.
동작 과정
[ 누적합 — 구간 합 질의 ]
arr = [ 3, 1, 4, 1, 5 ] (index 0~4)
prefix = [ 0, 3, 4, 8, 9, 14 ] (prefix[i] = arr[0..i-1]의 합, 길이 n+1)
구간 [1, 3]의 합 (arr[1]+arr[2]+arr[3] = 1+4+1 = 6) 을 구하려면:
rangeSum(1, 3) = prefix[3+1] - prefix[1] = prefix[4] - prefix[1] = 9 - 3 = 6
[ 차분 배열 — 구간에 값 일괄 더하기 ]
초기 배열 (전부 0), 길이 5 : [ 0, 0, 0, 0, 0 ]
diff 배열 (길이 n+1) : [ 0, 0, 0, 0, 0, 0 ]
rangeAdd(1, 3, 5) → index 1~3에 5를 더하고 싶다
diff[1] += 5 → diff = [ 0, 5, 0, 0, 0, 0 ]
diff[3 + 1] -= 5 → diff = [ 0, 5, 0, 0, -5, 0 ]
reconstruct(): diff를 앞에서부터 누적합하며 채운다
i=0: running=0+0=0 → arr[0]=0
i=1: running=0+5=5 → arr[1]=5
i=2: running=5+0=5 → arr[2]=5
i=3: running=5+0=5 → arr[3]=5
i=4: running=5-5=0 → arr[4]=0
결과 arr = [ 0, 5, 5, 5, 0 ] ← index 1~3만 5가 더해졌다
누적합은 "값을 미리 쌓아 두고 두 지점의 차로 구간 합을 꺼내는" 방식이고, 차분 배열은 반대로 "변화량만 양 끝에 기록해 두고 나중에 한 번에 누적해 복원하는" 방식입니다. 둘은 같은 누적 연산을 서로 반대 방향으로 쓰는 짝입니다.
구현
// 누적합 — 구간 합을 O(1)에 구하기 위한 전처리
int[] buildPrefixSum(int[] arr) {
int n = arr.length;
int[] prefix = new int[n + 1]; // prefix[i] = arr[0..i-1]의 합
for (int i = 0; i < n; i++) {
prefix[i + 1] = prefix[i] + arr[i];
}
return prefix;
}
int rangeSum(int[] prefix, int left, int right) { // arr[left..right] 구간 합, 0-indexed
return prefix[right + 1] - prefix[left];
}
// 차분 배열 — 구간에 값을 일괄로 더하는 작업을 O(1)에 처리
int[] diff;
void initDiff(int n) {
diff = new int[n + 1];
}
void rangeAdd(int left, int right, int value) { // arr[left..right]에 value를 더함
diff[left] += value;
diff[right + 1] -= value;
}
int[] reconstruct(int n) {
int[] arr = new int[n];
int running = 0;
for (int i = 0; i < n; i++) {
running += diff[i];
arr[i] = running;
}
return arr;
}
복잡도
| 구분 | 전처리/갱신 | 질의/복원 | 근거 |
|---|---|---|---|
| 누적합 | O(n) | O(1) | prefix 배열을 한 번만 채우면 두 값의 차로 모든 구간 합을 꺼낼 수 있다 |
| 차분 배열 | O(1) | O(n) | 구간 갱신마다 양 끝 두 칸만 수정, 전체 배열 복원은 한 번의 순회 |
둘 다 질의/갱신을 미리 모아 두고 한 번에 처리하는 오프라인 기법이라, 전처리 이후에는 원본 배열을 직접 건드리지 않는 것이 핵심입니다.
주의사항
- 1-indexed 전처리 배열 크기:
prefix와diff는 길이를n+1로 둬서left=0인 질의나right=n-1인 갱신에서 인덱스가 범위를 벗어나지 않게 한다 - 누적합은 배열이 고정일 때만 유리: 값을 수정한 뒤 다시 구간 합을 구해야 한다면 매번 O(n)으로 prefix를 다시 만들어야 해서 비효율적이다. 갱신과 질의가 번갈아 섞이는 경우에는 세그먼트 트리처럼 갱신과 질의를 모두 O(log n)에 처리하는 자료구조를 써야 한다
- 차분 배열은 "모든 갱신을 끝낸 뒤 한 번에 복원"하는 용도: 갱신 중간중간에 특정 지점의 현재 값을 바로 알아야 한다면 차분 배열만으로는 안 되고, 역시 세그먼트 트리가 필요하다
- 구간 끝 처리:
rangeAdd에서right + 1이 배열 길이를 넘지 않는지 확인한다.diff길이를n+1로 잡아 두면right = n-1일 때도 안전하다
핵심 정리
- 누적합: 구간 합을 O(1)에 구하기 위해 O(n) 전처리, 배열이 고정일 때 유리
- 차분 배열: 구간 일괄 더하기를 O(1)에 기록, 전체 복원은 O(n) — 모든 갱신이 끝난 뒤 한 번에 복원
- 둘 다 정적인(갱신이 드물거나 한 번에 몰리는) 상황에 적합하고, 갱신과 질의가 자주 번갈아 섞이면 세그먼트 트리를 고려한다