반응형
Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

웹풀스택 공부 중

DP: Dynamic Programming 이란? 본문

코딩테스트

DP: Dynamic Programming 이란?

lukeit 2024. 8. 20. 23:18

"동적 계획법"

 

- 알고리즘 설계 기법 및 최적화 이론의 한 기술

- 답을 재활용한다

   - 하나의 큰 문제를 작은 문제로 나누어 해결한다

   - 즉, 큰 문제를 풀기 위해 그 문제를 작은 문제로 나누고, 작은 문제를 푼 해법을 활용해서 큰 문제를 푼다

 

왜 Dynamic Programming인가?

   - Dynamic이란 단어가 멋있어서! (진짜임)

 

왜 DP가 중요한가?

- 적절한 문제에 DP를 사용하면 매우 빠르게 답을 얻을 수 있다

- 하지만 '적절한' 문제를 어떻게 판단하는가가 문제이다

 

DP의 사용 조건

1. Opitmal Substructure (최적 부분 구조)

   - Combining the optimal results of subproblems should lead to the optimal result of the bigger problem

   - 소문제들의 최적 결과 값을 이용해 전체 문제의 최적의 결과를 낼 수 있어야한다

2. Overlapping Subproblems (겹치는 소문제)

   - Same subproblems are solved repeatedly in different parts of the problem

   - 작은 문제의 결과를 "재사용"해서 원하는 결과를 도출할 수 있어야한다

 

예시:

A에서 C로 가고 싶은 경우, (1) A => B로 가는 경로, (2) B => C로 가는 경로로 문제를 나눠 각각 해결 후 더하면 A => C로 가는 경로가 완성 된다!

 

하지만 

이러한 경우엔 A => C로 가는 경로가 있기때문에 이전처럼 A-B, B-C로 나눠도 최적의 경로가 나오지 않는다. 즉, "최적 부분 구조"의 조건에 맞지 않는 경우이다.

 

 

DP로 문제 푸는 방법

- 방식:

   1. 메모하기: 변수 값에 따른 결과를 저장할 배열을 미리 만들고, 그 결과가 나올때마다 배열에 저장 후, 저장된 값을 재사용하는 방식

   2. 변수 간 관계식 만들기:  예시 - Fibonacci Sequence의 f(n) = f(n-1) + f(n-2)

 

- 두가지 방식:

   1. Top-Down Approach (Memoization)

      - 큰 문제를 작은 부분 문제로 나누며 해결하는 방식

      - 재귀 함수 (Recursion)을 사용하여 문제를 쪼게고, 중복 계산을 피하기 위해 이전에 계산한 값을 Memoize 한다

      - We start with the final solution and recursively break it down into smaller problems, sotre the results of the solved subproblems in a Memoization table

      - Suitable when there number of subproblems is large and many of them are reused

 

   2. Bottom-up Approach (Tabulation)

      - 작은 부분 문제부터 차례대로 해결하여 전체 문제를 해결하는 방식

      - 반복문을 사용하여 반복적으로 부분 문제들을 해결하며, 결과를 배열에 저장하는 방식

      - We start with the smallest subproblem and gradually build up to the final solution.

      - We store the results of solved subproblems in a table to avoid redundant calculations

 

 

DP를 자주 사용하는 알고리즘

1. Longest Common Subsequence (LCS): 

2. Shortest Path Problem: 

3. Knapsack Problem: 

4. Fibonacci Sequence:

 

 

예시 (출처)

1. Longest Increasing Subsequence (LCS): Bottom-Up

function longestIncreasingSubsequence(nums) {
  const n = nums.length;

  // 🌟 메모이제이션 🌟
  // 동적 계획법을 위한 배열 초기화
  const dp = new Array(n).fill(1);

  // 🌟 점화식 🌟
  // 최장 증가 부분 수열 계산
  for (let i = 1; i < n; i++) {
    for (let j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1);
      }
    }
  }

  // 결과 중 최대값 반환
  return Math.max(...dp);
}

// 예시로 배열 [10, 9, 2, 5, 3, 7, 101, 18]의 최장 증가 부분 수열의 길이 출력
const nums = [10, 9, 2, 5, 3, 7, 101, 18];

console.log(longestIncreasingSubsequence(nums));  // Output: 4

 

2. Shortest Path Problem: Bottom-Up

function shortestPath(graph, start, end) {
  const n = graph.length;
  // 🌟 메모이제이션 🌟
  const dp = new Array(n).fill(Infinity);
  dp[start] = 0;

  // 🌟 점화식 🌟
  for (let i = start; i <= end; i++) {
    for (let j = 0; j < n; j++) {
      if (graph[i][j] !== 0 && dp[i] + graph[i][j] < dp[j]) {
        dp[j] = dp[i] + graph[i][j];
      }
    }
  }

  return dp[end];
}

// 예시로 그래프와 시작점, 도착점이 주어졌을 때 최단 경로의 길이 출력
const graph = [
  [0, 4, 2, 0],
  [4, 0, 1, 5],
  [2, 1, 0, 8],
  [0, 5, 8, 0]
];
const start = 0;
const end = 3;

console.log(shortestPath(graph, start, end));  // Output: 6

 

3. Knapsack Problem: Bottom-Up

function knapsack(weights, values, capacity) {
  const n = weights.length;

  // 🌟 메모이제이션 🌟
  // 동적 계획법을 위한 2차원 배열 초기화
  const dp = new Array(n + 1);
  for (let i = 0; i <= n; i++) {
    dp[i] = new Array(capacity + 1).fill(0);
  }

  // 🌟 점화식 🌟
  // 배낭 문제 해결
  for (let i = 1; i <= n; i++) {
    for (let j = 1; j <= capacity; j++) {
      if (weights[i - 1] > j) {
        // 현재 아이템을 배낭에 넣을 수 없는 경우
        dp[i][j] = dp[i - 1][j];
      } else {
        // 현재 아이템을 배낭에 넣을 수 있는 경우
        dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1]);
      }
    }
  }

  // 결과 반환
  return dp[n][capacity];
}

// 예시로 아이템의 무게와 가치, 배낭의 용량이 주어졌을 때 최대 가치 출력
const weights = [2, 3, 4, 5];
const values = [3, 4, 5, 6];
const capacity = 7;

console.log(knapsack(weights, values, capacity));  // Output: 9

 

 

4. Fibonacci Problem: Top-down 

function fibonacci(n) {
  // 🌟 메모이제이션 🌟
  // 피보나치 수열을 저장할 배열
  const dp = new Array(n + 1);

  // 초기값 설정
  dp[0] = 0;
  dp[1] = 1;

  // 🌟 점화식 🌟
  // 피보나치 수열 계산
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }

  // 결과 반환
  return dp[n];
}

// 예시로 n = 10일 때 피보나치 수열 값 출력
console.log(fibonacci(10));  // Output: 55

 

반응형

'코딩테스트' 카테고리의 다른 글

Leetcode Q.1143 [Medium]: LCS  (0) 2024.08.21
Leetcode Q.1626 [Medium]: Knapsack DP  (0) 2024.08.20
Leetcode Q.200 [Medium]: DFS  (0) 2024.08.20