노현진's Blog

누적 합 (Prefix Sum) 알고리즘

누적 합 (Prefix Sum) 알고리즘에 대해 정리한 페이지입니다.

Posted
Preview Image
By HyunJinNo

Tags

Algorithm

1. 개요

누적 합(Prefix Sum) 알고리즘에 대해 정리한 페이지입니다.

2. 누적 합 (Prefix Sum) 알고리즘

2.1. 개념

누적 합(Prefix Sum) 알고리즘은 배열의 구간 합(부분 합)을 빠르게 구할 수 있게 해주는 알고리즘 기법을 의미합니다. 다음과 같이 scores라는 1차원 정수 배열이 있을 때 첫 번째 원소부터 특정 index까지의 누적 합 배열 S이 있으면 다음과 같이 정의할 수 있습니다.

index012345678
scores1009786796652494231
S100197283362428480529571602
javascript
1const scores = [100, 97, 86, 79, 66, 52, 49, 42, 31];
2
3const S = [100, 197, 283, 362, 428, 480, 529, 571, 602];

2.2. 시간 복잡도 (Time Complexity)

일반적으로 단순히 배열을 순회하면서 합을 구하면 한 번 계산할 때마다 O(N)의 시간이 걸립니다. 하지만 누적 합 배열을 미리 계산해두면 구간 합을 O(1)의 시간에 구할 수 있습니다. 특히 누적 합 알고리즘은 구간 합을 여러 번 계산해야 할 때 효율적입니다. 예를 들어 구간 합을 M번 구하는 문제가 있을 때, 단순 덧셈 방식은 O(N * M)의 시간이 걸리지만, 누적 합 알고리즘을 사용하면 누적 합 배열을 미리 계산해 두는데 걸리는 O(N) 시간이 소모된 이후에는 구간 합을 계산하는데 걸리는 시간이 거의 없으며, 총 O(N + M)의 시간 복잡도를 갖습니다.

단순 덧셈 방식과 누적 합 사용 방식의 시간 복잡도를 비교하면 다음과 같습니다.

방법구간 합 계산 시간전체 사전 준비 시간
단순 덧셈O(N)없음
누적 합 사용O(1)O(N)

2.3. 구현

2.3.1. 1차원 배열에서의 누적 합

javascript
1const scores = [10, 20, 30, 40, 50];
2const S = Array(scores.length).fill(0);
3S[0] = scores[0];
4
5for (let i = 1; i < scores.length; i++) {
6  S[i] = S[i - 1] + scores[i];
7}
8
9console.log(S); // [10, 30, 60, 100, 150]
10
11/**
12 * scores[a]부터 scores[b]까지의 누적 합을 구하는 함수
13 * @param {number} a 시작 인덱스
14 * @param {number} b 끝 인덱스
15 * @returns scores[a]부터 scores[b]까지의 누적 합
16 */
17const rangeSum = (a, b) => {
18  if (a === 0) {
19    return S[b];
20  } else {
21    return S[b] - S[a - 1];
22  }
23};
24
25console.log(rangeSum(1, 3)); // 90

2.3.2. 2차원 배열에서의 누적 합

javascript
1const arr = [
2  [10, 20, 30, 30],
3  [20, 20, 40, 30],
4  [50, 30, 20, 50],
5  [40, 40, 40, 40],
6];
7
8const S = Array.from({ length: arr.length }, () =>
9  Array(arr[0].length).fill(0),
10);
11
12// 각 행에 대한 누적 합 계산
13for (let i = 0; i < arr.length; i++) {
14  S[i][0] = arr[i][0];
15
16  for (let j = 1; j < arr[i].length; j++) {
17    S[i][j] = S[i][j - 1] + arr[i][j];
18  }
19}
20
21for (let i = 1; i < arr.length; i++) {
22  for (let j = 0; j < arr[i].length; j++) {
23    S[i][j] += S[i - 1][j];
24  }
25}
26
27/*
28[
29  [ 10, 30, 60, 90 ],
30  [ 30, 70, 140, 200 ],
31  [ 80, 150, 240, 350 ],
32  [ 120, 230, 360, 510 ]
33]
34*/
35console.log(S);
36
37/**
38 * arr[startRow][startCol]과 arr[endRow][endCol]를 양 끝으로 갖는 부분 배열의 합을 구하는 함수
39 * @param {number} startRow
40 * @param {number} startCol
41 * @param {number} endRow
42 * @param {number} endCol
43 * @returns arr[startRow][startCol]과 arr[endRow][endCol]를 양 끝으로 갖는 부분 배열의 합
44 */
45const rangeSum = (startRow, startCol, endRow, endCol) => {
46  let result = S[endRow][endCol];
47
48  if (startRow > 0) {
49    result -= S[startRow - 1][endCol];
50  }
51
52  if (startCol > 0) {
53    result -= S[endRow][startCol - 1];
54  }
55
56  if (startRow > 0 && startCol > 0) {
57    result += S[startRow - 1][startCol - 1];
58  }
59
60  return result;
61};
62
63console.log(rangeSum(1, 1, 3, 3)); // 310

3. Example

  • 1806번: 부분합

    javascript
    1const path =
    2  process.platform === "linux" ? "/dev/stdin" : "./JavaScript/input.txt";
    3const input = require("fs").readFileSync(path).toString().split("\n");
    4
    5// n: 수열의 길이, 10 <= n <= 100,000
    6// s: 합, 0 <= s <= 100,000,000
    7const [n, s] = input[0].split(" ").map(Number);
    8const arr = input[1].split(" ").map(Number);
    9const arrSum = Array(n).fill(0);
    10
    11arrSum[0] = arr[0];
    12for (let i = 1; i < n; i++) {
    13  arrSum[i] = arr[i] + arrSum[i - 1];
    14}
    15
    16/**
    17 * 시작 Index 부터 끝 Index 까지의 부분 합을 반환하는 함수
    18 * @param {number} a 시작 Index
    19 * @param {number} b 끝 Index
    20 * @returns {number} 부분 합
    21 */
    22const rangeSum = (a, b) => {
    23  if (a === 0) {
    24    return arrSum[b];
    25  } else {
    26    return arrSum[b] - arrSum[a - 1];
    27  }
    28};
    29
    30let answer = Infinity;
    31let a = 0;
    32let b = 0;
    33
    34while (b < n) {
    35  if (rangeSum(a, b) >= s) {
    36    answer = Math.min(answer, b - a + 1);
    37    a++;
    38  } else {
    39    b++;
    40  }
    41}
    42
    43console.log(`${answer === Infinity ? 0 : answer}`);

4. 참고 자료

© HyunJinNo. Some rights reserved.