Introduction to dynamic programming: Fibonacci sequence model and multi-state (C++)

A Brief Introduction to Dynamic Programming

    Dynamic programming (Dynamic programming, referred to as DP) is an algorithmic idea to solve multi-stage decision-making problems. It breaks down the problem into stages and improves efficiency by saving intermediate results to avoid double computation.


The problem-solving steps of dynamic programming are generally divided into the following steps:

  1. Thinking about state representation, creating a dp table (emphasis)
  2. Analyze the state transition equation (emphasis)
  3. initialization
  4. Determine the order of filling in the form
  5. Determine the return value

Fibonacci Sequence Model

1. The Nth Taibonacci number (simple)

Link: Nth Tibonacci Number

  • topic description

insert image description here

  • Question steps
  1. status indication

In the face of dynamic programming problems, we generally have two state representations:

  1. Starting from a certain location,...
  2. ending at a certain location, ...

We generally give priority to the first type of representation, but if the first type cannot be resolved, consider the second type.

insert image description here

  1. State transition equation
    This topic directly tells us the state transition equation: dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]

  2. Initialization The 0th, 1st, and 2nd positions
    of the Taibonacci numbers are special and do not satisfy the state transition equation, so we need to initialize these three positions as 0, 1, and 1

  3. The order of filling the form
    ensures that when filling the current state, the required state has been calculated, and the order of filling the form is obviously from left to right

  4. Return value
    According to the state representation, assuming that the request is for the nth one, the returned value should be dp[n]

  • Code
class Solution {
    
    
public:
    int tribonacci(int n) 
    {
    
    
        //对于第0、1、2单独处理
        if(n == 0) 
            return 0;
        if(n == 1 || n == 2)
            return 1;
        //dp[i]:第i个泰波那契数
        vector<int> dp(n + 1);
        dp[0] = 0; dp[1] = 1; dp[2] = 1; 
           
        for(int i = 3; i < n + 1; i++)
        {
    
    
            dp[i] = dp[i-1] + dp[i-2] + dp[i-3];
        }
        return dp[n];
        //空间复杂度:O(N)
        //时间复杂度:O(N)
    }
};

//不知道大家有没有发现向后填表的过程其实只需要前置的3个状态
//其余的状态都是多余的,我们可以用有限的变量来保存这些状态,这样就实现了空间优化
//这种优化方式被称为“滚动数组”
//经过优化原O(N)->O(1) O(N^2)->O(N)
//但这并不是动态规划讲解的要点,所以我只会把两种优化情况的代码给出

// class Solution {
    
    
// public:
//     int tribonacci(int n) 
//     {
    
    
//         if(n == 0) 
//             return 0;
//         if(n == 1 || n == 2)
//             return 1;

//         int t1 = 0;
//         int t2 = 1;
//         int t3 = 1;
//         int ret = 0;
           
//         for(int i = 3; i < n + 1; i++)
//         {
    
    
//             ret = t1 + t2 + t3;
//             t1 = t2;
//             t2 = t3;
//             t3 = ret;
//         }
//         return ret;
//     }
// };

2. Three-step question (simple)

Link: Three Step Questions

  • topic description
    insert image description here

  • Question steps

  1. status indication
    insert image description here

  2. State transition equation
    Arriving at stage i can be transformed into arriving at stage i - 3, i - 2, and i - 1 first, and the result is obtained by adding the three, so the state transition equation is: dp[i] = dp[i - 1] + dp [i - 2] + dp[i - 3].

  3. Initialization
    In order to ensure that the filling of the form does not exceed the boundary, we initialize the methods to reach the 1st, 2nd, and 3rd levels .

  4. The order of filling the form
    ensures that when filling the current state, the required state has been calculated, and the order of filling the form is from left to right .

  5. Return value
    According to the state representation, assuming that the requirement is nth order, the returned value should be dp[n]

  • Code
class Solution {
    
    
public:
    int waysToStep(int n) 
    {
    
    
        //1、2、3阶特殊处理
        if(n == 1) return 1;
        if(n == 2) return 2;
        if(n == 3) return 4;

        //dp[i]表示到达i阶的方法数
        vector<int> dp(n+1); //多开一个空间,可以让下标和层数对应
        dp[1] = 1; dp[2] = 2; dp[3] = 4;
        const int mod = 1e9 + 7;  //有可能超出,需要取模

        for(int i = 4; i < n + 1; i++)
        {
    
    
            dp[i] = ((dp[i-1] + dp[i-2]) % mod + dp[i-3]) % mod;
        }
        return dp[n];
        //时间复杂度:O(N)
        //空间复杂度:O(N)
    }
};

3. Use the minimum cost to climb stairs (easy)

Link: Climb stairs with minimum cost

  • topic description
    insert image description here

  • Question steps

  1. State Representation
    The idea of ​​this question is very similar to that of question 2. To reach the end point n, we can take one step from n - 1 and two steps from n - 2 to the end, and choose the one with the lowest cost (leave from the current level) needs to pay the leaving fee) ; as for the minimum fee to reach the n-1, n-2 level, we can analyze with the n-1, n-2 level as the end point, and so on. The process of reaching the end requires the minimum cost to reach each floor. We can store it in a dp table, and dp[i] represents the minimum cost required to reach the subscript i step .

  2. State transition equation
    The minimum cost to reach level i can be converted to min (minimum cost to reach level i - 1 + cost to get out of this level, minimum cost to reach i - level 2 + cost to get out of this level), so the state transition The equation is: dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]).

  3. Initialization
    From the transfer equation, it can be seen that updating a certain state requires two preceding states. In order to ensure that the table does not cross the boundary, the minimum cost of going to the 0th and 1st stages is separately processed .

  4. The order of filling the form
    ensures that when filling the current state, the required state has been calculated, and the order of filling the form is from left to right .

  5. Return value
    According to the state representation, assuming that the array has n elements (the end point is n order), the returned value should be dp[n]

  • Code
class Solution {
    
    
public:
    int minCostClimbingStairs(vector<int>& cost) 
    {
    
          
        //dp[i] 表示到这一层的最小花费
        int n = cost.size();
        vector<int> dp(n + 1);
        //一开始就可以在0或1阶,花费为0,vector默认给0,不用处理

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

        return dp[n];
        //空间复杂度:O(N)
        //时间复杂度:O(N)
    }
};

// //第二种写法:反着来,以某个位置为起点,……
// class Solution {
    
    
// public:
//     int minCostClimbingStairs(vector<int>& cost) 
//     {      
//         //dp[i]:这一层为起点,到终点的最低花费
//         int n = cost.size();
//         vector<int> dp(n + 1);
//         dp[n] = 0;
//         dp[n - 1] = cost[n - 1];
//         for(int i = n - 2; i >= 0; i--)
//         {            
//             dp[i] = min(dp[i + 1] + cost[i], dp[i + 2] + cost[i]);
//         }

//         return min(dp[0], dp[1]);
//     }
// };

4. Decoding method (medium)

Link: decoding method

  • topic description
    insert image description here

  • Question steps

  1. status indication
    insert image description here

  2. State representation
    Except for the first bit, each position has two modes of individual decoding and joint decoding. The state transition equation of position n is: dp[n] = dp[n - 1] (successful decoding alone) + dp[n - 2] (joint decoding successful)

  3. Initialization
    According to the state transition equation, the state of a certain position requires two pre-states . In order to avoid crossing the boundary, we need to deal with the first and second positions separately. However, observing the above analysis process, we can find that the second position is the same as other positions. There are two decoding possibilities. We can add a virtual node in front of the dp table and initially set it to 1 , so that only the first position needs to be processed. (Look at the picture to see the picture)

insert image description here

  1. The order of filling the form
    ensures that when filling the current state, the required state has been calculated, and the order of filling the form is from left to right .

  2. Return value
    According to the state definition, assuming that the sequence length is n, the return should be the decoding possible number ending at position n, that is, dp[n].

  • Code
class Solution {
    
    
public:
    int numDecodings(string s) 
    {
    
    
        int n = s.size();
        //dp[i]表示以i位置为结尾的解码可能数
        vector<int> dp(n + 1);
        //第一个位置就为0,最终结果已经是0
        if(s[0] == '0')
            return 0;
        //初始化虚拟节点和第1个位置
        dp[1] = dp[0] = 1;
        
        for(int i = 2; i < n + 1; i++)
        {
    
    
            //单独解码
            if(s[i - 1] != '0')                      
                dp[i] += dp[i-1];
            
            //联合解码(联合解码小于10说明存在前导0,无法联合解码)
            int com = (s[i - 2] - '0') * 10 + (s[i - 1] - '0');
            if(com >= 10 && com <= 26)                       
                dp[i] += dp[i-2];

            //都失败的情况是'00',最终结果已经是0,这里可不加
            //两个连续的0,后面全都是0
            if(dp[i] == 0)
                return 0;        
        }

        return dp[n];
    }
};

simple multi-state

1. House robbery (medium)

Link: Robbery

  • topic description
    insert image description here

  • Question steps

  1. State Representation
    According to the previous problem-solving experience, we can represent the state as the maximum amount stolen ending at position i , but each position has two options: stealing or not stealing , so the state can be further refined: state f represents End with position i and steal the maximum amount of this position; state g means end with position i but not steal the maximum amount of this position.

insert image description here

  1. The state transition equation
    can be seen from the previous analysis. To steal position i (f) requires the maximum amount of money not to steal (g) at position i - 1. If you do not steal position i, choose position i - 1. Steal or not steal the larger of the two options One side, so the state transition equation is:
    (1) f[i] = g[i - 1] + nums[ i ] (this position can steal money); (2)
    g[i] = max(g[i - 1 ], f[i - 1])

  2. Initialization
    From the state transition equation, it can be seen that the current state needs the previous state. To ensure that the table does not cross the boundary, the first position is processed separately: f[0] = nums[0], g[0] = 0 .

  3. The order of filling the form
    ensures that when filling the current state, the required state has been calculated, and the order of filling the form is from left to right .

  4. The return value
    substitutes itself into a thief. If adjacent positions cannot be stolen at the same time, a choice needs to be made, but the value of the house behind is not known during the stealing process, so one can only take one step at a time to ensure that each step is the best. , stealing to the end must be the optimal result. Assuming the array has n elements, the return value is max(f[n - 1], g[n - 1]).

  • Code
class Solution {
    
    
public:
    int rob(vector<int>& nums) 
    {
    
    
        int n = nums.size();
        vector<int> f(n); //f[i]表示到底这个位置并偷窃的最大金额
        auto g = f;  //g[i]表示到达这个位置不偷窃的最大金额
        f[0] = nums[0]; //初始化f[0],g[0]默认0不用处理
        for(int i = 1; i < n; i++)
        {
    
    
            f[i] = g[i - 1] + nums[i];
            g[i] = max(g[i - 1], f[i - 1]);
        }
        return max(f[n - 1], g[n - 1]);
        //空间复杂度:O(N)
        //时间复杂度:O(N)
    }
};

2. House robbery II (moderate)

Link: Robbery II

  • topic description
    insert image description here

  • Question steps

  1. State Representation
    The only difference between this question and the previous question is the point that the beginning and the end form a loop. We continue to use the state representation of the previous question: state f means ending at position i and stealing the maximum amount of this position; state g means The position ends without stealing the maximum amount of this position.
    The most direct way to deal with the loop formation problem is to dismantle it.
    insert image description here

  2. The state transition equation
    is consistent with the previous question, the state transition equation is:
    (1) f[i] = g[i - 1] + nums[ i ] (the amount that can be stolen in this position); (
    2) g[i] = max (g[i - 1], f[i - 1])

  3. Initialization
    is the same as the previous topic.

  4. Fill in the form
    from left to right.

  5. Return value
    The _rob function represents the robbery in the specified interval, and the return value is:
    max(nums[0] + _rob(nums, 2, n - 2), _rob(nums, 1, n - 1)) .

  • Code
class Solution {
    
    
public:
    int _rob(vector<int>& nums, int left,int right) 
    {
    
    
        //区间不存在返回0
        if(left > right)
            return 0;
        int n = nums.size();
        
        vector<int> f(n);  //到这个屋子偷的最大金额
        auto g = f; //到这个屋子不偷的最大金额
        f[left] = nums[left];

        for(int i = left + 1; i <= right; i++)
        {
    
    
            f[i] = g[i - 1] + nums[i];
            g[i] = max(f[i - 1],g[i - 1]);
        }
        return max(f[right],g[right]);
    }
    int rob(vector<int>& nums) 
    {
    
    
        int n = nums.size();
        return max(nums[0] + _rob(nums, 2, n - 2), _rob(nums, 1, n - 1));
    }
};

3. Paint the house (medium)

Link: Paint the house

  • topic description
    insert image description here

  • Question steps

  1. State Representation
    Based on experience and the requirements of the topic, we can define the state as the minimum cost of painting the i-th house with j color.
    insert image description here

  2. State transition equation
    The state transition equation is (0 is red, 1 is blue, 2 is green):
    (1)dp[i][0] = min(dp[i - 1][1], dp[i - 1 ][2]) + cost[i][0]
    (2)dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + cost[i][1]
    (3)dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + cost[i][2]

  3. Initialization
    In order to ensure that the filling of the form does not exceed the boundary, we need to initialize the value of the first row, but that is too troublesome, we can open an extra row and initialize 0, so that we do not need to process the first row separately . (Note the correspondence with the subscript of the cost array)

  4. Fill in the form
    from top to bottom, each line from left to right.

5. Return value
According to the state representation, assuming that the last house is number i, the return value is min({dp[n][0], dp[n][1], dp[n][2]}).

  • Code
class Solution {
    
    
public:
    int minCost(vector<vector<int>>& costs)
    {
    
    
        int n = costs.size();
        //dp[i][j]表示第i号房子粉刷成j颜色的最低花费
        //其中0表示红色,1表示蓝色,2表示绿色
        vector<vector<int>> dp(n + 1, vector<int>(3));
        //空间多开一行并初始化0,不用单独处理第一行

        for (int i = 1; i < n + 1; i++)
        {
    
    
            dp[i][0] = costs[i - 1][0] + min(dp[i - 1][1], dp[i - 1][2]);
            dp[i][1] = costs[i - 1][1] + min(dp[i - 1][0], dp[i - 1][2]);
            dp[i][2] = costs[i - 1][2] + min(dp[i - 1][0], dp[i - 1][1]);
        }
        return min({
    
    dp[n][0], dp[n][1], dp[n][2]});
        //时间复杂度:O(N)
        //空间复杂度:O(N)
    }
};

4. Delete and earn points (medium)

Link: delete and earn points

  • topic description
    insert image description here

  • Question steps

  1. status indication
    insert image description here

  2. State transition equation
    This question is a modified "fighting house", the transfer equation is the same:
    (1) f[i] = g[i - 1] + v[ i ] (delete this position to get points); (2) g[
    i ] = max(g[i - 1], f[i - 1])

  3. After the initialization
    array conversion is completed, the dp table does not need to be processed.

  4. Fill in the form
    from left to right.

  5. Return value
    The return value is max(f[N - 1],g[N - 1])

  • Code
class Solution {
    
    
public:
    int deleteAndEarn(vector<int>& nums)
    {
    
    
        int n = nums.size();
    
        //创建数组进行映射
        //题目中1 <= nums[i] <= 10000
        const int N = 10001;
        int v[N] = {
    
    0};
        for(auto val : nums)
            v[val] += val;

        //“打家劫舍”
        vector<int> f(N); //f[i]表示以i区域为结尾并且删除本区域的最大点数
        auto g = f;  //g[i]表示以i区域为结尾但不删除本区域的最大点数
        for (int i = 1; i < N; i++)
        {
    
    
            f[i] = g[i - 1] + v[i];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        
        return max(f[N - 1],g[N - 1]);
        //时间复杂度:O(N)
        //空间复杂度:O(1)
    }
};


//上面的写法简洁一些,但无论数据量多少都会遍历10000次
//可以记录数组的最大、最小值,来加快速度
// class Solution {
    
    
// public:
//     int deleteAndEarn(vector<int>& nums)
//     {
    
    
//         int n = nums.size();
//         vector<int> v(10001);
//         //先遍历一次
//         int _max = nums[0];
//         int _min = nums[0];
//         for (int i = 0; i < n; i++)
//         {
    
    
//             v[nums[i]] += nums[i];
//             _max = max(_max, nums[i]);
//             _min = min(_min, nums[i]);
//         }

//         vector<int> f(10001);
//         auto g = f;
//         for (int i = _min; i <= _max; i++)
//         {
    
    
//             f[i] = g[i - 1] + v[i];
//             g[i] = max(f[i - 1], g[i - 1]);
//         }
        
//         return max(f[_max],g[_max]);
//     }
// };

5. The best time to buy and sell stocks includes handling fees (medium)

Link: The best time to buy and sell stocks includes handling fees

  • topic description
    insert image description here

  • Question steps

  1. status indication
    insert image description here

dp[i][j]: maximum profit in state j at the end of day i.

  1. State transition equation, 0 means there is stock at the end, 1 means there is no stock at the end, fee is the handling fee, prices[i] means the stock price on the i-th day (1)
    dp[i][0] = max(dp[i - 1 ][0], dp[i - 1][1] - prices[i])
    (2)dp[i][1] = max(dp[i - 1][1], dp[i - 1][ 0] + prices[i] - fee)

  2. Initialization
    Just initialize the status of day 0, dp[0][0] -= prices[0]; .

  3. Fill in the form
    from top to bottom.

  4. Return Value
    The return value is: max(dp[n - 1][1], dp[n - 1][0]) .

  • Code
class Solution {
    
    
public:
    int maxProfit(vector<int>& prices, int fee) 
    {
    
    
        int n = prices.size();
        //dp[i][j]:第i天结束处于j状态的最大利润
        vector<vector<int>> dp(n, vector<int>(2));
        //这种解法买入还是卖出交手续费都一样(反正买入了一定会卖出)
        dp[0][0] -= prices[0];

        for(int i = 1; i < n; i++)
        {
    
    
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
        }
        return max(dp[n - 1][1], dp[n - 1][0]);
        //时间复杂度:O(N)
        //空间复杂度:O(N)
    }
};

6. The best time to buy and sell stocks includes a freezing period (medium)

Link: The best time to buy and sell stocks includes a freeze period

  • topic description
    insert image description here

  • Question steps

  1. status indication
    insert image description here

  2. The state transition equation
    0 is buying (with stock), 1 is tradable, and 2 is freezing, prices[i] represents the stock price on the i-th day, and the state transition equation is: (1)
    dp[i][0] = max (dp[i - 1][1] - prices[i], dp[i - 1][0])
    (2)dp[i][1] = max(dp[i - 1][2], dp [i - 1][1])
    (3)dp[i][2] = dp[i - 1][0] + prices[i]

  3. Initialization
    The three states of the current day need the state of the previous day, so initialize the first line
    dp[0][0] of the dp table: if you want to be in the buying state after the end of the day, you must buy the stock, dp[0][ 0] = -prices[i];
    dp[0][1]: do nothing, dp[0][1] = 0;
    dp[0][2]: want the end of the day to be frozen, on the same day buy and sell, dp[0][2] = 0;

  4. Fill in the form
    from top to bottom.

  5. Return value
    The maximum value should be no stock in hand, assuming the array has n elements, the maximum value is max(dp[n - 1][1], dp[n - 1][ 2 ]) .

  • Code
class Solution {
    
    
public:
    int maxProfit(vector<int>& prices) 
    {
    
    
        int n = prices.size();
        //dp[i][j]:第i天结束后处于j状态时的最大利润
        vector<vector<int>> dp(n, vector<int>(3));
        //初始化
        dp[0][0] -= prices[0];

        for(int i = 1; i < n; i++)
        {
    
    
            dp[i][0] = max(dp[i - 1][1] - prices[i], dp[i - 1][0]);
            dp[i][1] = max(dp[i - 1][2], dp[i - 1][1]);
            dp[i][2] = dp[i - 1][0] + prices[i];
        }
        
        return max(dp[n - 1][2],dp[n - 1][1]);
    }
};

7. Best Time to Buy and Sell Stocks III (Hard)

Link: Best Time to Buy and Sell Stocks III

  • topic description
    insert image description here

  • Question steps

  1. status indication
    insert image description here

  2. State transition equation
    According to the previous analysis, the state transition equation is:
    (1)f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i ])
    (2)if(j >= 1) g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i])
      else g[i][j] = g[i - 1][j]

  3. Initialization
    requires a state of i = 0, initializing the first row. (1) Only f[0][0] and g[0][0] exist in the first line, f[0][0] = -prices[0], g[0][0] =
    0 . (2) In order to avoid the non-existing state from interfering with the max value, we uniformly initialize the non-existing state to INT_MIN / 2 . (INT_MIN will be out of bounds, as small as possible)

  4. Fill
    in each column from top to bottom and each row from left to right.

  5. The return value
    returns the maximum value of the last row.

  • Code
class Solution {
    
    
public:
    //可能会越界,取INT_MIN的一半
    const int INF = INT_MIN / 2;
    int maxProfit(vector<int>& prices) {
    
    
        int n = prices.size();
        //dp[i][j]表示在第i天结束后完成j次交易,处于""状态下的最大利润
        vector<vector<int>> f(n, vector<int>(3, INF));  //买入
        auto g = f;  //可交易
        //初始化
        f[0][0] = -prices[0];
        g[0][0] = 0;

        for (int i = 1; i < n; i++)
        {
    
    
            for(int j = 0; j < 3; j++)
            {
    
    
                f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                //j == 0的时候前置状态f[i - 1][j - 1]不存在
                if(j >= 1)
                    g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }                                    
        }
        return max({
    
    g[n - 1][0], g[n - 1][1], g[n - 1][2]});
    }
};

8. Best Time to Buy and Sell Stocks IV (Difficult)

The way of thinking about this question is exactly the same as that of question 7. You can try to do it yourself first .
Link: Best Time to Buy and Sell Stocks IV

  • Code
class Solution {
    
    
public:
    const int INF = INT_MIN / 2;
    int maxProfit(int k, vector<int>& prices) 
    {
    
               
        int n = prices.size();
        //n天最多完成n / 2次交易,k不能超过这个值
        k = min(k, n / 2);
        //买入
        //dp[i][j]表示在第i天结束后完成j次交易,处于""状态下的最大利润
        vector<vector<int>> f(n, vector<int>(k + 1, INF));
        //卖出
        auto g = f;
        //初始化(先买再说)
        f[0][0] = -prices[0];
        g[0][0] = 0;

        for (int i = 1; i < n; i++)
        {
    
    
            for(int j = 0; j <= k; j++)
            {
    
    
                f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
                g[i][j] = g[i - 1][j];
                if(j >= 1)
                    g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
            }                                    
        }
        int ret = g[n - 1][0];
        //把利润最大的那个找出来
        for(int j = 1; j <= k; j++)
        {
    
    
            ret = max(ret, g[n - 1][j]);
        }
        return ret;
    }
};

Guess you like

Origin blog.csdn.net/2301_76269963/article/details/132457291