Dynamic programming is still a recursive algorithm in nature, but a recursive algorithm that meets certain conditions; Dynamic programming is a strong sense of design, a strong sense of art of an algorithm design.

For the original article visit my tech blog Tomato Tech Stack

What is dynamic programming

define

The original problem is decomposed into several sub-problems, and the answers of the sub-problems are saved at the same time, so that each sub-problem is solved only once, and finally the answer of the original problem is obtained.

  • We solve a problem with a small amount of data first, and then layer upon layer to solve a problem with a larger amount of data. This process is often called dynamic programming. This time is comparable to the time complexity of memorized search, but dynamic programming does not require recursive calls and requires no additional call and stack space.
  • Dynamic programming is a strong sense of design, a strong sense of art of an algorithm design.

A simple example


    #include <iostream>
    #include <ctime>
    
    using namespace std;
    
    int num = 0;
    
    int fib( int n ){
    
        num ++;
    
        if( n == 0 )
            return 0;
    
        if( n == 1 )
            return 1;
    
        return fib(n- 1) + fib(n2 -);
    }
    
    int main(a) {
    
        num = 0;
    
        int n = 42;
        time_t startTime = clock();
        int res = fib(n);
        time_t endTime = clock();
    
        cout<<"fib("<<n<<") ="<<res<<endl;
        cout<<"time : "<<double(endTime-startTime)/CLOCKS_PER_SEC<<" s"<<endl;
        cout<<"run function fib() "<<num<<"times."<<endl;
    
        return 0;
    }
Copy the code

Analysis of the

And if we look at the timing we’ll see that this algorithm is very slow, why is this solution so inefficient? When we need to compute FIB (5), its recursion tree is:

You can see from this diagram that there’s a lot of double counting going on here, and how do we avoid that? We can create an array memo outside the program, and the memo[I] actually memorizes the ith Fibonacci sequence.

    
    #include <iostream>
    #include <ctime>
    #include <vector>using namespace std; vector<int> memo; int num = 0; Int fib(int n){num ++;if( n == 0 )
            return 0;
    
        if( n == 1 )
            return 1;
    
        if( memo[n] == -1 )
            memo[n] = fib(n-1) + fib(n-2);
    
        return memo[n];
    }
    
    int main() {
    
        num = 0;
    
        int n = 42;
        memo = vector<int>(n+1,-1);
    
        time_t startTime = clock();
        int res = fib(n);
        time_t endTime = clock();
    
        cout<<"fib("<<n<<") ="<<res<<endl;
        cout<<"time : "<<double(endTime-startTime)/CLOCKS_PER_SEC<<" s"<<endl;
        cout<<"run function fib() "<<num<<"times."<<endl;
    
        return 0;
    }
    
Copy the code

We use an array of memos, so it’s called mnemonic search. Mnemonic search is just adding planning to recursion, it’s a top-down problem solving, and we assume that we’ve solved the basic problem, that we know fib of n minus one and FIB of n minus two, and then we can solve for the NTH number.

If we can solve problems from the top down, we can also solve problems from the bottom up, but most of the time we are used to the former.


    #include <iostream>
    #include <ctime>
    #include <vector>
    using namespace std;
    
    // Dynamic programming
    int fib( int n ){
    
        vector<int> memo(n+1.- 1);
    
        memo[0] = 0;
        memo[1] = 1;
        for( int i = 2 ; i <= n ; i ++ )
            memo[i] = memo[i- 1] + memo[i2 -];
    
        return memo[n];
    }
    
    int main(a) {
    
        // The result will overflow
        int n = 1000;
    
        time_t startTime = clock();
        int res = fib(n);
        time_t endTime = clock();
    
        cout<<"fib("<<n<<") ="<<res<<endl;
        cout<<"time : "<<double(endTime-startTime)/CLOCKS_PER_SEC<<" s"<<endl;
    
        return 0;
    }
    
Copy the code

The first dynamic programming problem

Leetcode 70. Take the stairs

Their thinking

Let’s think recursively about breaking up a big problem into smaller problems.

Code implementation (recursive)

#include <iostream>
    #include <vector>using namespace std; Class Solution {private: vector<int> memo; int calcWays(int n){if( n == 0 || n == 1)
                return 1;
    
            if( memo[n] == -1 )
                memo[n] = calcWays(n-1) + calcWays(n-2);
    
            return memo[n];
        }
    public:
        int climbStairs(int n) {
    
            memo = vector<int>(n+1,-1);
            returncalcWays(n); }};Copy the code

Code implementation (dynamic programming)

And we’ll see that just like Fibonacci above, it’s easy to convert to dynamic programming.

#include <iostream>
    #include <vector>using namespace std; Class Solution {public: int climbStairs(int n) {vector<int> Memo (n+1, -1); memo[0] = 1; memo[1] = 1;for ( int i = 2; i <= n; i++ ) {
                memo[i] = memo[i-1] + memo[i-2];
            }
    
            returnmemo[n]; }};Copy the code

Similar problems

  • leetcode 120
  • leetcode 64

Overlap subproblem found

Leetcode 343. Integer split

Their thinking

For a problem if there is no idea, we can first consider the violent solution. In other words, how do we enumerate all the partitions of the positive integer n, we don’t know how many loops there are, and we usually have to use recursion. Brute force solution: Backtrack through all the possibilities of dividing a number. O(2^n)

The recursion tree exists because it has the optimal substructure and by finding the optimal solution of the subproblem, you can get the optimal solution of the original problem.

Optimal substructure

  • By finding the optimal solution of the sub-problem, the optimal solution of the original problem can be obtained

Code implementation

To achieve 1
    #include <iostream>
    #include <cassert>
    
    using namespace std;
    
    class Solution {
    private:
        int max3( int a , int b , int c ){
            returnmax( a , max(b,c) ); } // The largest product int that can be obtained by dividing n (at least two parts)breakInteger( int n ){
    
            if( n == 1 )
                return 1;
    
            int res = -1;
            for( int i = 1 ; i <= n-1 ; i ++ )
                res = max3( res , i*(n-i) , i * breakInteger(n-i) );
            return res;
        }
    public:
        int integerBreak(int n) {
            assert( n >= 1 );
            return breakInteger(n); }};Copy the code
The 2

It contains overlapping subproblems, and here is the mnemonized search version:

    
    class Solution {
    private:
        vector<int> memo;
    
        int max3( int a , int b , int c ){
            returnmax( a , max(b,c) ); } // The largest product int that can be obtained by dividing n (at least two parts)breakInteger( int n ){
    
            if( n == 1 )
                return 1;
    
            if( memo[n] ! = 1)return memo[n];
    
            int res = -1;
            for( int i = 1 ; i <= n-1 ; i ++ )
                res = max3( res , i*(n-i) , i * breakInteger(n-i) );
            memo[n] = res;
            return res;
        }
    public:
        int integerBreak(int n) {
            assert( n >= 1 );
            memo = vector<int>(n+1, -1);
            return breakInteger(n); }};Copy the code
Realize 3 dynamic programming

Now let’s solve this problem using the bottom-up approach, which is dynamic programming

    
    class Solution {
    
    private:
        int max3( int a , int b , int c ){
            return max(max(a,b),c);
        }
    public:
        int integerBreak(int n) {// The memo[I] indicates the largest product of the number I. Vector <int> Memo (n+1, -1); memo[1] = 1;for( int i = 2; i <= n; I ++) {// Evaluate memo[I]for( int j = 1; j <= i-1; j++ ) { // j + (i-j) memo[i] = max3( memo[i], j*(i-j), j*memo[i-j] ); }}returnmemo[n]; }};Copy the code

Similar problems

  • leetcode 279
  • leetcode 91
  • leetcode 62
  • leetcode 63

State definition and state transition

Leetcode 198

Definition of state

Consider stealing houses in the range [x…n-1] (function definition)

Transition of state

F (0) = Max {v (0) + f (2), v (1) + f (3), v (2) + f (4),… , v(n-3) + F (n-1), V (n-2), v(n-1)}(state transition equation)

Their thinking

The first thing is again, if you don’t have an idea, consider the violent solution first. Check all the houses, for each combination, check if there are adjacent houses, if not, record their value, find the maximum. O((2^n)*n)

Note the definition of state: Consider stealing houses in the range [x… n-1] (function definition)

According to the definition of a state, the decision of the state of the transfer: f (0) = Max {v (0) + f (2), v (1) + f (3), v (2) + f (4),… , v(n-3) + F (n-1), V (n-2), v(n-1)}(state transition equation)

In fact, our recursive function is implementing state transitions.

198. House Robber

The implementation code

class Solution { private: // Memo [I] : consider robbing nums[I...n]. Vector <int> memo; Int tryRob(vector<int> &nums, int index){int tryRob(vector<int> &nums, int index){if( index >= nums.size() )
                return 0;
    
            if( memo[index] ! = 1)return memo[index];
    
            int res = 0;
            for( int i = index ; i < nums.size() ; i ++ )
                res = max(res, nums[i] + tryRob(nums, i+2));
            memo[index] = res;
            return res;
        }
    public:
        int rob(vector<int>& nums) {
    
            memo = vector<int>(nums.size(), -1);
            returntryRob(nums, 0); }};Copy the code

Dynamic programming solution

    
    class Solution {
    
    public:
        int rob(vector<int>& nums) {
    
            int n = nums.size();
    
            if( n == 0 ) {
                return0; Vector <int> memo(n, 0); memo[n-1] = nums[n-1];for( int i = n-2 ; i >= 0 ; i -- ) {
                for(int j = i; j < n; j++) { memo[i] = max(memo[i], nums[j] + (j + 2 < n ? memo[j + 2] : 0) ); }}returnmemo[0]; }};Copy the code

Another definition of state

What we emphasize is that for dynamic programming, we need to be clear about our definition of state. In our previous definition, we were thinking about stealing houses in the range [x… n-1]. Many times we can set up different states to get the same correct answer to the same question.

Change the definition of state: consider stealing houses in the range [0… x] (function definition). The implementation is as follows:

Memorized search code implementation

class Solution { private: vector<int> memo; Int tryRob(vector<int>&nums, int index){if (index < 0){
            return 0;
        }
        
        if(memo[index] ! = 1) {return memo[index];
        }
        
        int res = 0;
        for( int i = index; i >= 0; i--){
            res = max(res, nums[i] + tryRob(nums, i - 2));
        }
        memo[index] = res;
        return res;
    }

public:
    
    int rob(vector<int>& nums) {
        int n = nums.size();
        memo = vector<int>(n + 1, -1);
        if (n == 0){
            return 0;
        }
        
        returntryRob(nums, n-1); }};Copy the code

Dynamic programming code implementation

Int rob(vector<int> &nums) {int n = nums.size(); vector<int> memo(n, -1);if (n == 0){
            return 0;
        }
        
        memo[0] = nums[0];
        
        for(int i = 1; i < n; i++){
            for(int j = i; j >= 0; j --){ memo[i] = max(memo[i], nums[j] + (j-2 >= 0? memo[j-2]: 0)); }}returnmemo[n-1]; }};Copy the code

Similar problems

  • leetcode 213
  • leetcode 337
  • leetcode 309

— — — — — — — — — — — — — — — — — — — — — — — — — gorgeous line — — — — — — — — — — — — — — — — — — — —

After watching the friends can click a like/follow, your support is the biggest encouragement to me.

Personal blog tomato technology small stack and gold digging home page

If you want to know more, please follow my wechat official account: Tomato Technology Xiaozhan