5. 알고리즘 다이나믹 프로그래밍

다이나믹 프로그래밍(Dynamic Programming)은 문제를 여러 개의 하위 문제로 나누어 해결하고, 중복되는 하위 문제의 결과를 저장하여 중복 계산을 피하는 최적화 기법입니다. 다이나믹 프로그래밍은 큰 문제를 작은 하위 문제로 분할하여 해결하고, 작은 하위 문제의 해결 결과를 이용하여 최적 해를 찾아내는 방법입니다.

다이나믹 프로그래밍의 특징

1. 중복된 하위 문제

다이나믹 프로그래밍에서는 문제를 여러 개의 하위 문제로 나누고, 이 하위 문제들이 중복되는 특성을 가지는 경우가 많습니다. 따라서 이 중복되는 하위 문제들의 결과를 저장하고, 필요할 때 재사용하여 중복 계산을 피합니다.

2. 최적 부분 구조

다이나믹 프로그래밍은 주어진 문제의 최적해가 작은 부분 문제들의 최적해로부터 구성될 수 있는 경우에 적용할 수 있습니다. 큰 문제를 작은 문제로 나누고, 작은 문제들의 최적해를 이용하여 큰 문제의 최적해를 구하는 방식입니다.

다이나믹 프로그래밍의 과정

1. 문제 정의

큰 문제를 작은 하위 문제로 나누고, 작은 하위 문제의 최적해로부터 큰 문제의 최적해를 구하는 방식을 정의합니다.

2. 하위 문제 정의

큰 문제를 작은 하위 문제로 나누는 방식과 하위 문제들 간의 관계를 정의합니다. 이 때, 중복되는 하위 문제들을 찾고, 필요한 경우 결과를 저장하여 재사용합니다.

3. 하위 문제의 해결과 저장

작은 하위 문제들을 해결하고, 그 결과를 저장합니다. 이를 위해 메모이제이션(Memoization) 기법을 사용하여 하위 문제들의 결과를 저장하고 재사용합니다.

4. 최종 해결

모든 하위 문제들의 해결과 저장이 완료되면, 이를 결합하여 큰 문제의 최적해를 도출합니다.

다이나믹 프로그래밍은 중복되는 계산을 효율적으로 제거하고, 최적해를 찾아내는 효과적인 알고리즘 기법입니다. 다이나믹 프로그래밍은 문제의 특성에 따라 적용 가능한 경우가 있으며, 예시로는 피보나치 수열, 배낭 문제, 최단 경로 문제 등이 있습니다.

다이나믹 프로그래밍의 예시

1. 피보나치 수열(Fibonacci Sequence)

피보나치 수열은 이전 두 개의 숫자를 더하여 다음 숫자를 만들어내는 수열입니다. 다이나믹 프로그래밍을 사용하여 피보나치 수열을 구하는 방법은 중복 계산을 피하기 위해 이전에 계산한 값을 저장하고 재사용하는 것입니다.

public static int fibonacci(int n) {
    if (n == 0 || n == 1) {
        return n;
    }

    int[] dp = new int[n + 1];
    dp[0] = 0;
    dp[1] = 1;

    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }

    return dp[n];
}
  • fibonacci 메서드는 주어진 숫자 n에 대한 피보나치 수열 값을 반환합니다.

  • dp 배열을 사용하여 이전에 계산한 값을 저장하고 재사용합니다.

  • 반복문을 통해 dp[i] 값을 계산하면서 n까지 모든 값을 구합니다.

2. 배낭 문제(Knapsack Problem)

배낭 문제는 주어진 가방의 용량에 따라 가치와 무게가 다른 물건들을 선택하여 최대 가치를 담는 문제입니다. 다이나믹 프로그래밍을 사용하여 부분 문제의 최적해를 저장하고 재사용함으로써 모든 가능한 조합을 탐색하지 않고도 최적해를 찾을 수 있습니다.

public static int knapsack(int[] weights, int[] values, int capacity) {
    int n = weights.length;
    int[][] dp = new int[n + 1][capacity + 1];

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= capacity; j++) {
            if (weights[i - 1] <= j) {
                dp[i][j] = Math.max(values[i - 1] + dp[i - 1][j - weights[i - 1]], dp[i - 1][j]);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }

    return dp[n][capacity];
}
  • knapsack 메서드는 주어진 무게와 가치의 배열(weights, values)과 배낭의 용량(capacity)을 입력으로 받아 최대 가치를 반환합니다.

  • dp 2차원 배열을 사용하여 부분 문제의 최적해를 저장하고, 이를 활용하여 전체 최적해를 구합니다.

  • 이중 반복문을 사용하여 각 물건에 대해 배낭의 용량에 따른 최대 가치를 계산합니다.

3. 최장 증가 부분 수열(Longest Increasing Subsequence)

주어진 수열에서 가장 긴 증가하는 부분 수열을 찾는 문제입니다. 다이나믹 프로그래밍을 사용하여 부분 문제의 최적해를 저장하고 재사용하여 길이를 구할 수 있습니다.

public static int longestIncreasingSubsequence(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    Arrays.fill(dp, 1);

    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }

    int maxLength = 0;
    for (int length : dp) {
        maxLength = Math.max(maxLength, length);
    }

    return maxLength;
}
  • longestIncreasingSubsequence 메서드는 주어진 배열 nums에서 최장 증가 부분 수열의 길이를 반환합니다.

  • dp 배열을 사용하여 각 원소의 위치에서의 최장 증가 부분 수열의 길이를 저장합니다.

  • 이중 반복문을 통해 이전 위치의 값과 현재 위치의 값 비교를 통해 dp[i] 값을 갱신합니다.

  • 마지막으로 dp 배열에서 가장 큰 값을 찾아 최장 증가 부분 수열의 길이를 구합니다.

4. 최단 경로 문제(Shortest Path Problem)

그래프에서 두 정점 사이의 최단 경로를 찾는 문제입니다. 다이나믹 프로그래밍을 사용하여 이전에 계산한 최단 거리 값을 저장하고, 작은 문제들을 해결하여 전체 최단 경로를 구할 수 있습니다.

public static int longestIncreasingSubsequence(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    Arrays.fill(dp, 1);

    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }

    int maxLength = 0;
    for (int length : dp) {
        maxLength = Math.max(maxLength, length);
    }

    return maxLength;
}
  • shortestPath 메서드는 주어진 그래프 grid에서 출발점부터 도착점까지의 최단 경로의 거리를 반환합니다.

  • dp 2차원 배열을 사용하여 각 위치에서의 최단 거리를 저장합니다.

  • 이중 반복문을 사용하여 최단 거리를 계산하면서 dp[i][j] 값을 갱신합니다.

  • 마지막으로 도착점의 dp 값을 반환하여 최단 경로의 거리를 구합니다.

5. 문자열 편집 거리(String Edit Distance)

두 개의 문자열 사이의 최소 편집 거리를 계산하는 문제입니다. 다이나믹 프로그래밍을 사용하여 이전에 계산한 부분 문제의 최소 거리를 저장하고, 작은 문제들을 해결하여 전체 편집 거리를 구할 수 있습니다.

public static int editDistance(String word1, String word2) {
    int m = word1.length();
    int n = word2.length();
    int[][] dp = new int[m + 1][n + 1];

    for (int i = 0; i <= m; i++) {
        dp[i][0] = i;
    }

    for (int j = 0; j <= n; j++) {
        dp[0][j] = j;
    }

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1;
            }
        }
    }

    return dp[m][n];
}
  • editDistance 메서드는 두 개의 문자열 word1word2 사이의 최소 편집 거리를 반환합니다.

  • dp 2차원 배열을 사용하여 각 위치에서의 최소 편집 거리를 저장합니다.

  • 이중 반복문을 사용하여 문자열의 각 문자를 비교하고, 편집 연산(insert, delete, replace)에 따른 dp[i][j] 값을 갱신합니다.

  • 마지막으로 두 문자열의 전체 길이에 대한 dp 값을 반환하여 최소 편집 거리를 구합니다.

위의 코드는 각 문제를 다이나믹 프로그래밍으로 해결하는 방법을 간단히 보여주는 예시입니다. 실제 문제에 따라 코드의 구현이 다소 복잡해질 수 있으며, 입력 데이터의 크기에 따라 성능이 달라질 수 있습니다. 다이나믹프로그래밍을 사용하여 이러한 문제를 효율적으로 해결할 수 있으며, 각 문제마다 최적의 알고리즘이 존재할 수 있습니다.

Last updated