자바 코딩테스트 강좌, 구간 합 구하기 2

저자: 조광형

작성일: 2024년 11월 26일

1. 문제 설명

이번 시간에는 구간 합 문제를 다뤄 보겠습니다. 알고리즘 및 코딩 테스트에서 자주 접할 수 있는 문제 중 하나로, 주어진 배열의 특정 구간의 합을 효율적으로 계산하는 방법을 배워보겠습니다.

문제는 다음과 같습니다.

정수로 이루어진 배열 A와 다양한 쿼리들이 주어질 때, 각 쿼리에서 제시한 구간 l, r에 대하여 A[l] 부터 A[r] 까지의 합을 구하시오.

입력 형식:

  1. 배열 A의 크기 N (1 ≤ N ≤ 100,000)
  2. 배열 A의 원소 (1 ≤ A[i] ≤ 100,000)
  3. 쿼리의 개수 Q (1 ≤ Q ≤ 100,000)
  4. 각 쿼리는 두 개의 정수 l, r를 포함하며 (1 ≤ l ≤ r ≤ N).

출력 형식:

각 쿼리에 대한 구간 합을 출력하시오.

2. 접근 방법

문제의 접근 방법은 두 가지로 나눌 수 있습니다:

  1. 단순하게 반복문을 사용하여 합계를 계산하는 방법 (비효율적)
  2. 누적 합 배열을 사용하여 O(1) 시간 내에 구간 합을 구하는 방법 (효율적)

첫 번째 접근 방법의 경우, 각 쿼리에 대해 구간의 합을 직접 계산하면 시간 복잡도가 쿼리 개수 Q와 배열 크기 N의 곱이 되어 O(N * Q)입니다. 이 경우, 최악의 경우 1010번의 연산이 필요해 효율적이지 않습니다.

두 번째 접근 방법은 누적 합 배열을 생성하여 O(1) 시간 내에 구간 합을 구하는 것입니다. 이 방법의 전체적인 접근 방식은 다음과 같습니다:

  1. 우선 N 크기의 누적 합 배열을 생성합니다.
  2. 누적 합 배열의 i번 인덱스에는 A[0]부터 A[i]까지의 합이 저장됩니다.
  3. 각 쿼리 (l, r)의 합은 누적 합 배열을 이용하여 S[r] – S[l-1]로 계산할 수 있습니다.

3. 코드 구현

      public class IntervalSum {
        public static void main(String[] args) {
            int N = 5; // 배열 크기
            int[] A = {1, 2, 3, 4, 5}; // 입력 값
            int Q = 3; // 쿼리 수
            int[][] queries = {{1, 3}, {2, 5}, {1, 5}}; // 쿼리 예시
            
            // 누적 합 배열 생성
            long[] prefixSum = new long[N + 1];
            for (int i = 1; i <= N; i++) {
                prefixSum[i] = prefixSum[i - 1] + A[i - 1];
            }
            
            // 각 쿼리 처리
            for (int i = 0; i < Q; i++) {
                int l = queries[i][0];
                int r = queries[i][1];
                long sum = prefixSum[r] - prefixSum[l - 1];
                System.out.println("Sum from " + l + " to " + r + ": " + sum);
            }
        }
      }
    

4. 코드 설명

위 코드는 다음과 같은 절차로 동작합니다:

  1. 배열 A와 쿼리 개수 Q, 각 쿼리의 l, r을 입력받습니다.
  2. 우선 누적 합 배열을 생성합니다. 이 배열의 i번 인덱스에는 배열 A의 0번 인덱스부터 i-1 인덱스까지의 합이 저장됩니다.
  3. 각 쿼리를 처리할 때, 해당하는 구간의 합은 prefixSum[r] – prefixSum[l-1]으로 계산하여 출력합니다.

5. 시간 복잡도 분석

이 알고리즘의 시간 복잡도는 다음과 같습니다:

  1. 누적 합 배열을 생성하는 데 O(N) 시간이 소요됩니다.
  2. 각 쿼리를 처리하는 데 O(1) 시간이 소요됩니다.

따라서 전체 시간 복잡도는 O(N + Q)입니다. 이는 매우 효율적인 방법으로, 배열의 크기와 쿼리의 개수가 모두 최대값인 경우에도 빠른 성능을 보입니다.

6. 마지막 정리

구간 합 구하기 문제는 알고리즘 및 데이터 구조를 이해하는 데 중요한 개념입니다. 이번 강좌에서는 누적 합 배열을 이용한 효율적인 구간 합 계산 방법을 배웠습니다. 이 방법을 통해 많은 양의 데이터를 처리할 때 성능을 극대화할 수 있습니다.

이러한 종류의 문제는 또한 다양한 변형 문제로 접할 수 있으므로 추가적으로 연습하는 것이 중요합니다. 앞으로도 다양한 알고리즘 문제를 풀어보며 더욱 깊고 넓은 이해를 쌓아 나가길 바랍니다.

좋아요와 구독은 큰 힘이 됩니다! 더 많은 알고리즘 강좌를 기대해 주세요.

자바 코딩테스트 강좌, 구간 합

안녕하세요! 이번 포스팅에서는 자바를 활용한 코딩테스트 준비를 위한 구간 합 문제를 다루어 보겠습니다. 구간 합 문제는 주어진 배열의 특정 구간에 있는 원소들의 합을 효율적으로 구하는 문제로, 알고리즘 문제에서 자주 출제되는 유형입니다.

문제 설명

배열 A가 주어졌을 때, Q개의 쿼리가 있습니다. 각 쿼리는 두 정수 LR로 이루어져 있으며, 이들은 배열 A의 인덱스를 나타냅니다. 각 쿼리에 대해 L번째부터 R번째까지의 구간 합을 구하시오.

입력

    첫 번째 줄에는 배열의 크기 N과 쿼리의 수 Q가 주어진다.
    두 번째 줄에는 N개의 정수 A[1], A[2], ..., A[N]이 주어진다.
    이후 Q개의 줄에 LR가 공백으로 구분되어 주어진다.
    

출력

    각 쿼리에 대한 구간 합을 한 줄에 하나씩 출력한다.
    

예제 입력

    5 3
    1 2 3 4 5
    1 3
    2 4
    1 5
    

예제 출력

    6
    9
    15
    

문제 해결 전략

구간 합 문제를 처리하기 위한 코딩 방법은 여러 가지가 있지만, 비효율적인 방법으로는 각 쿼리에 대해 직접 루프를 돌며 구간 합을 계산하는 것입니다. 이 경우, 최악의 경우 O(N * Q)의 시간이 소요될 수 있습니다. 따라서 우리는 구간 합을 빠르게 계산하기 위한 방법을 찾아야 합니다.

전처리 통한 접근

효율적인 방법 중 하나는 전처리 과정을 통해 구간 합을 누적 배열 형태로 저장하는 것입니다. 이렇게 하면 각 쿼리의 구간 합을 O(1)에 구할 수 있습니다.

  1. 먼저, 누적합 배열 sum을 생성합니다. 이때 sum[0] = 0으로 초기화하고, sum[i] = sum[i - 1] + A[i - 1]로 설정합니다.
  2. 각 쿼리는 구간 [L, R]에서의 합을 구하기 위해 sum[R] - sum[L - 1] 공식을 사용합니다.

자바 코드 구현

    import java.util.Scanner;

    public class RangeSum {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            
            // 배열의 크기와 쿼리 수 입력
            int N = sc.nextInt();
            int Q = sc.nextInt();
            
            // 배열 입력
            int[] A = new int[N];
            for (int i = 0; i < N; i++) {
                A[i] = sc.nextInt();
            }
            
            // 누적합 배열 생성
            long[] sum = new long[N + 1];
            for (int i = 1; i <= N; i++) {
                sum[i] = sum[i - 1] + A[i - 1];
            }
            
            // 각 쿼리를 처리
            for (int i = 0; i < Q; i++) {
                int L = sc.nextInt();
                int R = sc.nextInt();
                long rangeSum = sum[R] - sum[L - 1];
                System.out.println(rangeSum);
            }
            
            sc.close();
        }
    }
    

코드 설명

위의 코드는 다음과 같은 흐름으로 동작합니다:

  1. 먼저, 사용자로부터 배열의 크기 N과 쿼리 수 Q를 입력받습니다.
  2. 그 다음, 배열 A의 원소를 입력받아 초기화합니다.
  3. 각 원소의 누적합을 저장하는 sum 배열을 생성합니다. 이때, 인덱스 0은 0으로 초기화 통합하여, sum[i]에 대한 접근이 용이합니다.
  4. 쿼리마다 주어진 LR을 이용해 구간 합을 계산 후 출력합니다.

테스트 및 검증

코드를 작성한 후, 다양한 입력을 통해 각 쿼리가 올바른 결과를 출력하는지 확인해야 합니다. 예제 입력과 함께 추가적인 경우들도 실험해보며 결과를 검토합니다. 아래는 몇 가지 예시입니다.

  • 입력: 1 1 → 출력: 1
  • 입력: 2 5 → 출력: 14
  • 입력: 3 3 → 출력: 3

결론

이번 포스팅에서는 구간 합 문제를 해결하는 과정을 살펴보았습니다. 전처리를 통해 구간 합을 효율적으로 계산하는 방법을 익히는 것이 중요하며, 자바의 배열과 반복문을 활용하는 방법을 연습하는 것도 좋은 코딩 실력을 기르는 데 도움이 될 것입니다. 앞으로 더 많은 알고리즘 문제를 다루며 기술적인 역량을 다져나가길 바랍니다.

감사합니다!

자바 코딩테스트 강좌, 구간 합 구하기 1

안녕하세요! 오늘은 자바 코딩테스트에서 빈번하게 출제되는 알고리즘 중 하나인 구간 합 구하기에 대해 깊이 있게 살펴보려고 합니다. 특히, 이 강좌에서는 구간 합을 구하는 알고리즘 문제를 하나 선정하여 그 풀이 과정을 자세히 설명하겠습니다.

문제 정의

주어진 정수 배열 arr가 있을 때, 여러 쿼리가 주어집니다. 각 쿼리는 두 정수 startend를 포함하며, 이 경우 arr[start] + arr[start + 1] + ... + arr[end]를 구하는 문제입니다. 이 문제에서 요구하는 것은 각 쿼리에 대해 구간 합을 효율적으로 계산하는 것입니다.

문제 예시

    입력:
    arr = [1, 2, 3, 4, 5]
    쿼리 = [[0, 2], [1, 3], [2, 4]]
    
    출력:
    [6, 9, 12]
    

풀이 알고리즘

이 문제를 해결하기 위해서는 배열에 대한 여러 번의 구간 합을 효율적으로 처리할 수 있어야 합니다. 기본적으로, 각 쿼리마다 배열의 일부를 반복하여 합산하는 방법은 비효율적일 수 있습니다. 이러한 비효율성을 줄이기 위해 “구간 합 배열”을 활용한 접근 방법을 소개하겠습니다.

구간 합 배열 설명

구간 합 배열(`prefix sum array`)은 주어진 배열의 원소를 누적하여 전처리하는 방법입니다. 이를 통해 각 구간 합을 O(1)의 시간 복잡도 내에 계산할 수 있습니다. 구간 합 배열의 정의는 다음과 같습니다:

    prefix[i] = arr[0] + arr[1] + ... + arr[i]
    

즉, prefixSum[j] - prefixSum[i-1]로 구간 arr[i] ~ arr[j]의 합을 구할 수 있습니다. 여기서 prefixSum[-1]은 0으로 가정합니다.

구현 단계

  1. 구간 합 배열 생성: 주어진 배열을 이용해 구간 합 배열을 생성합니다.
  2. 쿼리 처리: 각 쿼리에 대해 구간 합을 O(1) 복잡도로 계산합니다.

자바 코드 구현

    public class RangeSum {
        public static void main(String[] args) {
            int[] arr = {1, 2, 3, 4, 5};
            int[][] queries = {{0, 2}, {1, 3}, {2, 4}};
            int[] result = rangeSum(arr, queries);
            
            for (int sum : result) {
                System.out.println(sum);
            }
        }

        public static int[] rangeSum(int[] arr, int[][] queries) {
            int n = arr.length;
            int[] prefixSum = new int[n];
            prefixSum[0] = arr[0];

            // 누적합 배열 생성
            for (int i = 1; i < n; i++) {
                prefixSum[i] = prefixSum[i - 1] + arr[i];
            }

            int[] results = new int[queries.length];
            for (int i = 0; i < queries.length; i++) {
                int start = queries[i][0];
                int end = queries[i][1];

                // 구간 합 계산
                results[i] = (start > 0) ? (prefixSum[end] - prefixSum[start - 1]) : prefixSum[end];
            }

            return results;
        }
    }
    

코드 설명

위의 자바 코드는 사용자 정의 클래스 RangeSum을 만들고, main 메소드에서 배열과 쿼리를 정의합니다. rangeSum 메소드는 주어진 배열로부터 구간 합 배열을 만들어 각 쿼리의 구간 합을 계산하는 기능을 수행합니다.

1. 누적합 배열 생성

먼저 첫 번째 원소를 초기화하고, 반복문을 통해 누적합 배열을 만듭니다. 이 과정은 O(n) 시간 복잡도를 가지며, n은 배열의 길이입니다.

2. 쿼리 처리

각 쿼리에 대해 누적합 배열을 활용하여 O(1)로 구간 합을 계산합니다. 데이터가 많거나 쿼리가 많을 경우 이 방법이 성능상 유리할 것입니다.

복잡도 분석

이 코드의 시간 복잡도는 O(n + m)입니다. 여기서 n은 배열의 길이이고, m은 쿼리의 수입니다. 따라서 초기화 과정에서 O(n)의 시간 복잡도를 소모하고, 각 쿼리에 대해 O(1)를 소모하므로 전체 시간 복잡도는 O(n + m)입니다. 이를 통해 준수한 성능을 보장받을 수 있습니다.

결론

오늘은 자바 코딩테스트에서 구간 합을 구하는 문제를 다루어 보았습니다. 배열의 누적합 배열을 이용하면 여러 쿼리에 대해 효율적으로 구간 합을 계산할 수 있음을 알 수 있었습니다. 실제 코딩테스트에 응용해보면 유용한 알고리즘이므로 꼭 기억해 두시기 바랍니다! 앞으로도 더 많은 알고리즘 문제풀이 강좌를 준비하겠습니다. 감사합니다!

자바 코딩테스트 강좌, 구간 곱 구하기

안녕하세요! 이번 포스팅에서는 구간 곱 구하기 문제를 해결하는 방법에 대해 알아보겠습니다. 이 문제는 주어진 배열에서 특정 구간의 곱을 빠르게 계산하는 알고리즘을 구현하는 것을 목표로 합니다. 이 강좌에서는 문제 정의부터 시작하여, 접근 방식, 구현, 그리고 성능 분석까지 단계적으로 진행됩니다.

문제 정의

주어진 정수 배열 nums와 여러 쿼리 (i, j)가 있을 때, 배열의 인덱스 i부터 j까지의 곱을 구해야 합니다. 이를 여러 번 반복해야 하므로, 시간 복잡도를 고려하여 효율적인 방법이 필요합니다.

예제

입력:
nums = [1, 2, 3, 4, 5]
쿼리 = [(0, 1), (1, 3), (0, 4)]

출력:
쿼리 0: 1 * 2 = 2
쿼리 1: 2 * 3 * 4 = 24
쿼리 2: 1 * 2 * 3 * 4 * 5 = 120

접근 방법

직관적으로는 각 쿼리에 대해 nums 배열에서 직접 곱을 계산할 수 있습니다. 하지만 이는 최악의 경우 O(n) 시간 복잡도를 요구하므로 여러 쿼리를 수행할 때 비효율적입니다. 따라서 아래와 같은 두 가지 접근 방법을 고려해볼 수 있습니다:

  1. 누적 곱 배열 (Prefix Product Array): 배열의 누적 곱을 미리 계산한 후, 각 쿼리에 대해 O(1)로 남은 곱을 빠르게 계산하는 방법입니다. 이 방법은 O(n)으로 누적 곱 배열을 준비한 후, 각 쿼리에서 O(1)에 계산하여 총 O(n + m) (여기서 m은 쿼리의 개수) 이 될 수 있습니다.
  2. 세그먼트 트리 (Segment Tree): 더 일반적인 방법으로, 세그먼트 트리를 사용하여 각 구간의 곱을 효율적으로 계산할 수 있습니다. 이 경우 초기화는 O(n log n)이고 쿼리는 O(log n)입니다.

이번 강좌에서는 첫 번째 방법인 누적 곱 배열을 사용한 구현을 진행하겠습니다.

구현

먼저, 누적 곱 배열을 생성한 다음, 각 쿼리마다 해당 구간의 곱을 계산해보겠습니다.

public class RangeProduct {
    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4, 5};
        int[][] queries = {{0, 1}, {1, 3}, {0, 4}};
        int[] result = rangeProduct(nums, queries);
        
        for (int res : result) {
            System.out.println(res);
        }
    }

    public static int[] rangeProduct(int[] nums, int[][] queries) {
        int n = nums.length;
        int[] prefixProduct = new int[n + 1];
        
        // 누적 곱 배열 생성
        prefixProduct[0] = 1; // 초기값
        for (int i = 1; i <= n; i++) {
            prefixProduct[i] = prefixProduct[i - 1] * nums[i - 1];
        }
        
        int[] result = new int[queries.length];

        // 쿼리 처리
        for (int q = 0; q < queries.length; q++) {
            int i = queries[q][0];
            int j = queries[q][1];
            // 구간곱 계산
            result[q] = prefixProduct[j + 1] / prefixProduct[i]; // 0 ~ j의 곱 / 0 ~ (i-1)의 곱
        }
        
        return result;
    }
}

성능 분석

위의 풀이는 효율적으로 쿼리를 처리할 수 있는 방법을 보여줍니다. 초기 누적 곱 배열을 만드는 데 O(n)시간이 걸리며, 각 쿼리 처리 시간은 O(1)입니다. 총 시간 복잡도는 O(n + m)이 됩니다. 이 방법은 querry 수가 많아질수록 효율적인 성능을 보여줍니다.

마무리

이번 강좌에서는 구간 곱 구하기 문제를 해결하는 방법을 살펴보았습니다. 누적 곱 배열을 사용하여 효율적으로 쿼리를 처리하는 방법을 배울 수 있었습니다. 세그먼트 트리나 펜윅 트리와 같은 더욱 복잡한 자료구조를 배우고 싶다면 다음 강좌를 기대해 주세요!

더 나아가기

이 강좌 이후에는 해당 문제를 다른 방식으로 해결하는 방법에 대해 다루어보는 것도 좋습니다. 다양한 방법론을 학습함으로써 알고리즘적 사고력을 확장할 수 있을 것입니다. 질문이나 토론이 필요한 부분이 있다면 댓글로 남겨주세요!

자바 코딩테스트 강좌, 계단 수 구하기

코딩테스트에 대비하기 위한 여러 가지 알고리즘 문제를 풀어보며 실제 시험에서의 문제 해결 능력을 키워봅시다.

문제 설명

계단 수는 다음과 같은 규칙을 따릅니다. n자리 수이며, 각 자리 숫자는 오름차순으로 증가해야 합니다. 즉, 인접한 두 자리 수의 차이는 정확히 1이어야 합니다. 예를 들어, 123, 234, 345는 모두 계단 수입니다.

문제

N이 주어질 때, N자리 계단 수의 개수를 구하는 프로그램을 작성하세요.

입력

  • 첫 번째 줄에 자연수 N이 주어집니다. (1 ≤ N ≤ 100)

출력

  • N자리 계단 수의 개수를 10007로 나눈 나머지를 출력합니다.

예제 입력

3

예제 출력

14

설명

N이 3이면, 3자리 계단 수는 123, 132, 210, 321, 234, 345, 456, 567, 678, 789 등 총 14개가 존재합니다.

문제 풀이 과정

이 문제를 해결하기 위해서는 다이나믹 프로그래밍(Dynamic Programming)의 기법을 사용할 수 있습니다.

계단 수는 이전 N-1자리 수를 기반으로 한 N자리 수의 조합으로 생각할 수 있습니다. N-1자리 수의 마지막 숫자에 따라 N자리 수의 마지막 숫자가 결정되므로, 이를 고려하여 DP 배열을 업데이트하면 됩니다.

풀이 접근 방식

  1. 상태 정의: dp[n][last]를 n자리 계단 수 중 마지막 자리 수가 last인 경우의 개수로 정의합니다. n은 자리 수를 의미하며, last는 마지막 숫자(0~9)를 의미합니다.
  2. 초기 상태 설정: 1자리 수의 경우(즉, N=1일 때) 각 숫자(1~9)는 각기 1개의 경우의 수를 가집니다. 따라서 dp[1][1] ~ dp[1][9]는 1, dp[1][0]는 0입니다.
  3. 상태 전이: n자리 수는 n-1자리 수에서 만들어집니다. 마지막 숫자가 last일 때, 마지막 숫자는 last-1 또는 last+1일 수 있습니다. 이를 수식으로 표현하면 다음과 같습니다.

                        dp[n][last] = dp[n-1][last-1] + dp[n-1][last+1]
                        

    여기서 last는 1부터 8까지의 범위를 가질 수 있습니다(0과 9는 각각 한쪽에 한정되므로).

  4. 결과 계산: N자리 수의 개수는 dp[N][0] + dp[N][1] + … + dp[N][9]의 합입니다. 그러나 문제에서 요구하는 것은 10007로 나눈 나머지이므로, 계산 과정에서 이 연산을 수행해야 합니다.

자바 코드 구현

                import java.util.Scanner;

                public class StairNumber {
                    public static void main(String[] args) {
                        Scanner sc = new Scanner(System.in);
                        int N = sc.nextInt();
                        int MOD = 10007;

                        // DP 배열 초기화
                        int[][] dp = new int[N + 1][10];

                        // 1자리 수 초기화
                        for (int i = 1; i < 10; i++) {
                            dp[1][i] = 1;
                        }

                        // DP 테이블 구성
                        for (int n = 2; n <= N; n++) {
                            for (int last = 0; last <= 9; last++) {
                                if (last > 0) { // last가 0이 아닐 경우
                                    dp[n][last] += dp[n - 1][last - 1];
                                }
                                if (last < 9) { // last가 9가 아닐 경우
                                    dp[n][last] += dp[n - 1][last + 1];
                                }
                                dp[n][last] %= MOD; // MOD 연산
                            }
                        }

                        // 결과 합산
                        int result = 0;
                        for (int last = 0; last <= 9; last++) {
                            result = (result + dp[N][last]) % MOD;
                        }

                        // 결과 출력
                        System.out.println(result);
                        sc.close();
                    }
                }
            

예시

입력

3

출력

14

위의 코드를 실행하게 되면, 3자리 계단 수의 개수를 정확하게 구할 수 있습니다.

추가 사항

문제를 해결하고 난 후, 다양한 테스트 케이스로 알고리즘의 정확성을 검증하는 것이 중요합니다. 또한 코드를 최적화하거나 다양한 방법으로 알고리즘을 접근하여 그 결과를 비교하는 것도 좋은 방법입니다.

마지막으로, 다이나믹 프로그래밍 문제는 많은 경우 그래프 이론과도 연결될 수 있으니, 항상 이러한 관점을 갖고 문제를 푸셔야 합니다.

많은 도움이 되길 바랍니다! 기초적인 알고리즘 부터 시작하여 점점 더 복잡한 문제로 나아가길 바랍니다.