Zero must-read series 3 dynamic programming problem-solving routine framework

table of Contents

1. Fibonacci sequence

1. Violent recursion

2. Recursive solution with memo

3. Dynamic programming solution

2. The problem of collecting change


The dynamic programming problem is generally a problem of seeking the most value.

Three elements of dynamic programming: overlapping sub-problems, optimal sub-structures, and state transition equations.

The state transition equation is the most difficult, the author provides a thinking framework:

Clear "state" -> define the meaning of dp array/function -> clear "choice" -> clear base case.

Start with examples.

1. Fibonacci sequence

Find f(n).

1. Violent recursion

int fib(int N) {
    if (N == 1 || N == 2) {
        return 1;
    }

    return fib(N - 1) + fib(N - 2);
}

Recursive algorithm time complexity calculation:

  • Number of sub-problems: number of binary tree nodes, O(2^n).
  • Each sub-problem time: f(n-1) + f(n-2) such an addition operation, O(1).

Therefore, the time complexity of this algorithm is O(2^n).

However, such calculations will have overlapping sub-problems and consume a lot of time. For example, calculating f(20) requires calculating f(19) and f(18), and calculating f(19) will calculate f(18) and f(17), so that f(18) is calculated twice. The overlapping subproblem is the first property of the dynamic programming problem.

2. Recursive solution with memo

Create a memo and save all the calculated sub-questions to avoid double calculations.

inf fib(int N) {
    if (N < 1) {
        return 0;
    }

    // 备忘录全初始化为0
    vector<int> memo(N + 1, 0);

    return helper(memo, N);
}

int helper(vector<int>& memo, int n) {
    // base case
    if (n == 1 || n == 2) {
        return 1;
    }

    // 已经计算过
    if (memo[n] != 0) {
        return memo[n];
    }

    memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
    return memo[n];
}

time complexity:

  • Number of sub-problems: no redundant calculation, O(n).
  • Time for each sub-question: O(1).

Therefore, the time complexity of the algorithm is O(n).

The algorithm is top-down, and dynamic programming is bottom-up.

3. Dynamic programming solution

Separate the memo into a table, called dp table. Calculate from the bottom up.

int fib(int N) {
    vector<int> dp(N + 1, 0);
    // base case
    dp[1] = 1;
    dp[2] = 1;
    
    for (int i = 3; i <= N; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    
    return dp[N];
}

Re-optimization: Since the current state is only related to the previous two states, so long dp table is not needed. code show as below:

int fib(int n) {
    if (n == 1 || n == 2) {
        return 1;
    }

    int pre = 1;
    int cur = 1;
    for (int i = 3; i <= n; i++) {
        int sum = pre + cur;
        pre = cur;
        cur = sum;
    }

    return sum;
}

Introducing the concept: state transition equation

f(n) is a state, and the state f(n) is transferred from the addition of f(n-1) and f(n-2), nothing more. The state transition equation is a brute force solution, and the dynamic programming is optimized using memo or dp table.

2. The problem of collecting change

 kDenominations of coins in denominations of c1, c2 ... ckunlimited number of each coin, to give a total amount amount, ask you at least need a few coins Couchu this amount, if not impossible Couchu, the algorithm returns -1. For example k = 3, the face value is 1, 2, 5, and the total amount amount = 11. Then at least 3 coins are needed to make up, that is, 11 = 5 + 5 + 1.

  1. Clarify the status: the amount of change in the original problem and sub-problems. The total amount amout.
  2. Determine the definition of the dp function: the current target amount is n, and at least dp(n) coins are needed to collect.
  3. Decide on the choice and choose the best: what choices can be made to change the state.
  4. Determine the base case: dp[0] = 0
int coinChange(vector<int>& coins, int amount) {
    // 数组大小为 amount + 1,初始值也为 amount + 1
    vector<int> dp(amount + 1, amount + 1);
    // base case
    dp[0] = 0;
    for (int i = 0; i < dp.size(); i++) {
        // 内层 for 在求所有子问题 + 1 的最小值
        for (int coin : coins) {
            // 子问题无解,跳过
            if (i - coin < 0) continue;
            dp[i] = min(dp[i], 1 + dp[i - coin]);
        }
    }
    return (dp[amount] == amount + 1) ? -1 : dp[amount];
}

 

Guess you like

Origin blog.csdn.net/weixin_36389889/article/details/105082198