Coding Test/Codility

5. λˆ„μ  ν•© (Prefix Sums) Codility Lesson ν•œκ΅­μ–΄ 정리본 (JavaScript ver.)

chamroro 2025. 3. 1. 23:28

λˆ„μ  ν•©(Prefix Sums)μ΄λž€?

λ°°μ—΄μ˜ νŠΉμ • ꡬ간(슬라이슀)의 합을 λΉ λ₯΄κ²Œ κ³„μ‚°ν•˜λŠ” κ°•λ ₯ν•œ 기법이 λˆ„μ  ν•©(Prefix Sums) 이닀. μ΄λŠ” λ°°μ—΄μ˜ μ²˜μŒλΆ€ν„° νŠΉμ • μœ„μΉ˜κΉŒμ§€μ˜ 합을 미리 계산해 μ €μž₯ν•˜λŠ” 방법이닀.

예λ₯Ό λ“€μ–΄, λ°°μ—΄ Aκ°€ λ‹€μŒκ³Ό κ°™λ‹€κ³  ν•˜μž.

A = [aβ‚€, a₁, aβ‚‚, ..., aₙ₋₁]

그러면 λˆ„μ  ν•© λ°°μ—΄ PλŠ” λ‹€μŒκ³Ό 같이 μ •μ˜λœλ‹€.

P[0] = 0
P[1] = aβ‚€
P[2] = aβ‚€ + a₁
P[3] = aβ‚€ + a₁ + aβ‚‚
...
P[n] = aβ‚€ + a₁ + ... + aₙ₋₁

이전 κ°’ P[k-1]에 ν˜„μž¬ κ°’ A[k-1]을 λ”ν•˜λŠ” λ°©μ‹μœΌλ‘œ λˆ„μ  합을 계산할 수 있으며, 전체 연산은 O(n) 의 μ‹œκ°„ λ³΅μž‘λ„λ₯Ό κ°€μ§„λ‹€.


1. O(n) μ‹œκ°„ λ³΅μž‘λ„μ˜ λˆ„μ  ν•© 계산

JavaScript μ½”λ“œ:

function prefixSums(A) {
    let n = A.length;
    let P = new Array(n + 1).fill(0);
    for (let k = 1; k <= n; k++) {
        P[k] = P[k - 1] + A[k - 1];
    }
    return P;
}

이 ν•¨μˆ˜λŠ” μž…λ ₯ λ°°μ—΄ A의 λˆ„μ  합을 κ³„μ‚°ν•˜μ—¬ λ°˜ν™˜ν•œλ‹€.


2. O(1) μ‹œκ°„ λ³΅μž‘λ„μ˜ ꡬ간 ν•© 계산

λˆ„μ  합을 ν™œμš©ν•˜λ©΄ νŠΉμ • ꡬ간 [x..y]의 합을 O(1) λ§Œμ— ꡬ할 수 μžˆλ‹€.

ꡬ간 ν•©: P[y + 1] - P[x]

JavaScript μ½”λ“œ:

function countTotal(P, x, y) {
    return P[y + 1] - P[x];
}

이 방법을 μ‚¬μš©ν•˜λ©΄ O(n + m) μ‹œκ°„ λ³΅μž‘λ„λ‘œ μ—¬λŸ¬ 개의 ꡬ간 합을 λΉ λ₯΄κ²Œ 계산할 수 μžˆλ‹€.


3. 버섯 쀍기 문제

λ„λ‘œ μœ„ νŠΉμ • μœ„μΉ˜ kμ—μ„œ μ‹œμž‘ν•˜μ—¬ μ΅œλŒ€ m번 μ΄λ™ν•˜λ©΄μ„œ 버섯을 μ΅œλŒ€ν•œ 많이 μˆ˜μ§‘ν•˜λŠ” λ¬Έμ œμ΄λ‹€. 이동은 μΈμ ‘ν•œ μœ„μΉ˜λ‘œλ§Œ κ°€λŠ₯ν•˜λ‹€.

O(n²) (λΉ„νš¨μœ¨μ μΈ 방법)

function slowMushrooms(A, k, m) {
    let n = A.length;
    let result = 0;
    for (let p = 0; p <= m; p++) {
        let left = k - p;
        let right = Math.min(n - 1, k + (m - 2 * p));
        let sum = 0;
        for (let i = left; i <= right; i++) {
            sum += A[i];
        }
        result = Math.max(result, sum);
    }
    return result;
}

이 방법은 O(m²) μ‹œκ°„μ΄ μ†Œμš”λœλ‹€.


O(n + m) (효율적인 방법)

λˆ„μ  합을 μ΄μš©ν•˜μ—¬ O(n + m) μ‹œκ°„ λ³΅μž‘λ„λ‘œ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.

function mushrooms(A, k, m) {
    let n = A.length;
    let result = 0;
    let P = prefixSums(A);
    
    for (let p = 0; p <= Math.min(m, k); p++) {
        let left = k - p;
        let right = Math.min(n - 1, Math.max(k, k + m - 2 * p));
        result = Math.max(result, countTotal(P, left, right));
    }
    
    for (let p = 0; p <= Math.min(m, n - k - 1); p++) {
        let right = k + p;
        let left = Math.max(0, Math.min(k, k - (m - 2 * p)));
        result = Math.max(result, countTotal(P, left, right));
    }
    
    return result;
}

이 방법은 λˆ„μ  합을 ν™œμš©ν•˜μ—¬ 슬라이슀 합을 O(1)에 κ³„μ‚°ν•˜λ―€λ‘œ 전체 연산을 O(n + m) μ‹œκ°„ λ³΅μž‘λ„λ‘œ μˆ˜ν–‰ν•  수 μžˆλ‹€.

λ°˜μ‘ν˜•