leetcode dynamic planning

leetcode dynamic planning

Article catalog


Dynamic Programming, a branch of operational research, is a mathematical method to solve the optimization of decision-making process.

Keywords: global optimal solution, original problem and subproblem, dynamic planning state, boundary state knot value, turntable transfer.

LeetCode 70 - Climbing Stairs - easy

Suppose you are climbing the stairs. You need n steps to get to the top of the building.

You can climb one or two steps at a time. How many different ways can you climb to the top of the building?

Note: given n is a positive integer.

Example 1:

Input: 2
 Output: 2
 Explanation: there are two ways to climb to the top.
1. Level 1 + level 1
 2. Stage 2

Example 2:

Input: 3
 Output: 3
 Explanation: there are three ways to climb to the top.
1. Level 1 + level 1 + level 1
 2. Level 1 + level 2
 3. Level 2 + level 1

Recursive timeout solution:

It's actually the Fibonacci series

class Solution {
public:
    int climbStairs(int n) {
        if (n == 1 || n == 2) return n;
        return climbStairs(n - 1) + climbStairs(n - 2);
    }
};

There are too many repeated calculations, and the time complexity approaches to the nth power of 2.

Dynamic programming solution

Think about the number of climbs to the i-th level. The answer is the sum of the number of climbing methods of i-1 and i-2 (because only one or two steps can be climbed at a time).

Step:

  1. Set the recurrence array dp[n]={0}, dp is the abbreviation of dynamic planning. There are several climbing methods from dp[i] to the i-th order;
  2. dp[1] = 1; dp[2] = 2;
  3. Determine how many steps there are in order 3 to n
#include <vector>

class Solution {
public:
    int climbStairs(int n) {
        std::vector<int> dp(n + 3, 0);
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; ++i)
        {
            dp[i] = dp[i - 1] + dp[i -2];
        }
        return dp[n];
    }
};

The reason why there are n+3 elements is that the array must have at least 0, 1 and 2 steps. If it's n+1, n==0 is out of bounds.

Summary of 4 elements of dynamic planning:

  1. Confirm the original problem and sub problem. The former is the number of N steps, the latter is the number of 1 to n-1 steps;
  2. Confirm the status. The state of ontology is simple, i.e. the number of i-step walking method;
  3. Boundary state. Step 2.
  4. State transition. That is, the statements in the loop.

Leetcode 198 - house robbing - easy

You are a professional thief, planning to steal houses along the street. There is a certain amount of cash in each room. The only restriction to your theft is that the adjacent houses are equipped with interconnected anti-theft system. If two adjacent houses are intruded by thieves in the same night, the system will automatically alarm.

Given an array of non negative integers representing the amount stored in each house, calculate the maximum amount you can steal overnight without touching the alarm.

Example 1:

Input: [1,2,3,1]
Output: 4
 Explanation: steal house 1 (amount = 1), then house 3 (amount = 3).
     Maximum amount stolen = 1 + 3 = 4.

Example 2:

Input: [2,7,9,3,1]
Output: 12
 Explanation: steal house 1 (amount = 2), house 3 (amount = 9), and then house 5 (amount = 1).
     Maximum amount stolen = 2 + 9 + 1 = 12.

Tips:

0 <= nums.length <= 100
0 <= nums[i] <= 400

Timeout solution

Each room has two states, the time complexity is n power of 2.

Greedy algorithm, do not trigger the alarm premise, each time choose the most treasures. Definitely not~

Dynamic programming solution

If i room is selected, i-1 cannot be selected; if i is not selected, only the first i-1 room will be considered.

4 key elements analysis:

  1. In the original problem, the optimal solution of N rooms is obtained; in the sub problem, the optimal solution of the first 1, n-1 rooms is obtained.
  2. Status. The i-th status is the largest treasure obtained by the first i-room.
  3. Boundary state. dp[1] is the first room treasure, dp[2] is the larger treasure in room 1 and 2.
  4. State transition.
    • Select I, dp[i] as the optimal solution of the ith room + the former i-2 room;
    • If i is not selected, the optimal solution of the first i-1 room is taken
#include <vector>
#include <algorithm>
class Solution {
public:
    int rob(vector<int>& nums) {
        unsigned int nSize = nums.size();
        if(nSize == 0) return 0;
        if(nSize == 1) return nums[0];

        std::vector<int> dp(nSize + 3, 0);
        dp[1] = nums[0];
        dp[2] = std::max(nums[0], nums[1]);
        for(unsigned int i = 3; i <= nSize; ++i)
        {
            dp[i] = std::max(
                dp[i - 1],
                dp[i - 2] + nums[i-1]);
        }

        return dp[nSize];
    }
};

LeetCode 53 - Maximum Subarray - Maximum subarray and - easy

Very classical dynamic programming problem.

Given an integer array nums, find a continuous subarray with the largest sum (subarray contains at least one element), and return its maximum sum.

Example:

Input: [- 2,1, - 3,4, - 1,2,1, - 5,4],
Output: 6
 Explanation: the sum of continuous subarrays [4, - 1,2,1] is the largest, which is - 6.

In case of violent enumeration, the complexity is the second power of n.

Direct dynamic programming. The key is to determine the planning status. Consider using dp[i] to represent the largest sub segment sum composed of the first I numbers, and push it to the relationship of dp[i], dp[i-1], and find that it is not possible, for example:

[-2, 1, 1, -3, 4]
dp[4]:[1,1]
dp[5]:[4]

That is, the state transition relationship cannot be deduced.

In order to make the optimal solution of dp[i], dp[i-1] two states have a connection, that is to say, according to the latter to deduce the former, dp[i] can represent the optimal solution of the sub segment ending with the ith number.

#include <vector>
using namespace std;
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        unsigned int nSize = nums.size();
        if(nSize == 0) return 0;
        
        int nRes = 0;
        vector<int> dp(nums.size(), 0);

        dp[0] = nums[0];
        nRes = dp[0];
        
        for(unsigned int i = 1; i < nSize; ++i)
        {
            dp[i] = max(dp[i - 1] + nums[i], nums[i]);

            nRes = nRes > dp[i]
                ? nRes
                : dp[i];
        }
        return nRes;
    }
};

LeetCode 322 - Coin Change - change - medium

Give coins of different denominations and a total amount. Write a function to calculate the minimum number of coins needed to make up the total amount. If no combination of coins can make up the total amount, return - 1.

Example 1:

Input: coins = [1, 2, 5], amount = 11
 Output: 3 
Explanation: 11 = 5 + 5 + 1

Example 2:

Input: coins = [2], amount = 3
 Output: - 1

Note: you can think of the number of coins of each kind as infinite.

Other solutions

Greedy thinking: choose the coin with the largest denomination each time.

Counter example: [1, 2, 5, 7, 10], 14, positive solution is two 7, greedy is [10, 2, 2], wrong..

Dynamic programming solution

4 key elements analysis:

  1. The original problem is the total N optimal solution; the subproblem is the optimal solution from the sum of money 1 to n-1.
  2. Status. The ith state is the optimal solution of amount i, that is, the minimum number of coins.
  3. Boundary state. The corresponding status of each face value is 1, and the rest are initialized to - 1.
  4. State transition. For example 1, dp[i-1], dp[i-2], dp[i-5] are combined with three denominations, i.e. after taking the minimum value respectively, add 1.
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        
        vector<int> dp(amount + 1, -1);
        unsigned int nSize = coins.size();
        
        //This initialization can be omitted, because dp[0]=0 is assigned, and dp [coin value] will be assigned as 0 + 1 during derivation
        //for(unsigned int i = 0; i < nSize; ++i)
        //{
        //    dp[coins[i]] = 1;
        //}
        
        dp[0] = 0;
        for(unsigned int i = 1; i <= amount; ++i)
        {
            for(unsigned int j = 0; j < nSize; ++j)
            {
                // Can't cross the boundary, and dp[i-coins[j] has a solution
                if ( i < coins[j] || dp[i - coins[j]] == -1) continue;

                if ( dp[i] == -1 || dp[i] > dp[i-coins[j]] + 1)
                {
                    dp[i] = dp[ i - coins[j] ] + 1;
                }
            }
        }
        return dp[amount];
    }
};

LeetCode 120 - Triangle - medium

Given a triangle, find the minimum path sum from top to bottom. Each step can only be moved to the adjacent nodes in the next row.

Adjacent nodes here refer to two nodes whose subscripts are the same as or equal to the subscripts + 1 of the nodes in the previous layer.

For example, given a triangle:

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]

The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).

Note: if you can only use O(n) extra space (n is the total number of triangles) to solve this problem, then your algorithm will be very bonus.

Greedy algorithm is definitely not good (there is a number 100000 below).

Starting from this question, release the two-dimensional dp array.

4 key elements analysis:

  1. Original problem, N-level optimal solution; subproblem, top angle to n-1 level optimal solution.
  2. Status. dp[i][j] represents the optimal solution of the distance between the ith row and the jth column of the triangle.
  3. Boundary state. dp[0][0] is the top angle value, others are 0. Or initialize the last line first and push backward.
  4. State transition. Two derivation methods, from top angle to bottom layer, or from bottom layer to top angle.

From top to bottom

Obviously it's going to be a little bit more trouble.

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        unsigned int nRol = triangle.size();
        if(nRol == 0) return 0;

        vector<vector<int>> dp;

        // Initialize the top two edges
        for(unsigned int i = 0; i < nRol; ++i)
        {
            unsigned int nCol = triangle[i].size();
            dp.push_back(vector<int>(nCol, 0));
            
            if(nCol)
            {
                if(i == 0)
                {
                    // Apex angle
                    dp[i][0] = triangle[0][0];
                }
                else
                {
                    dp[i][0] = triangle[i][0] + dp[i - 1][0];
                    dp[i][nCol - 1] = triangle[i][nCol - 1] + dp[i - 1][nCol - 2];
                }
            }
        }

        
        for(unsigned int rol = 1; rol <= nRol - 1; ++rol)
        {
            unsigned int nCol = triangle[rol].size();
            for(unsigned int col = 1; col <= nCol - 2; ++col)
            {
                dp[rol][col] = ( dp[rol - 1][col - 1] < dp[rol - 1][col]
                    ? dp[rol - 1][col - 1]
                    : dp[rol - 1][col] )
                    + triangle[rol][col];
            }
        }
        return *min_element(dp[nRol - 1].begin(), dp[nRol - 1].end());
    }
};

From bottom to top

Obviously more concise.

It should be noted that the row and column variables are decremented during the loop, and the type of unsigned int cannot be used.

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        int nRol = triangle.size();
        if(nRol == 0) return 0;

        vector<vector<int>> dp;

        // Initialize bottom edge
        for(int i = 0; i < nRol - 1; ++i)
        {
            int nCol = triangle[i].size();
            dp.push_back(vector<int>(nCol, 0));
        }
        dp.push_back(triangle[nRol - 1]);

        
        for(int rol = nRol - 2; rol >= 0; --rol)
        {
            int nCol = triangle[rol].size();
            for(int col = 0; col < nCol; ++col)
            {
                dp[rol][col] = ( dp[rol + 1][col] < dp[rol + 1][col + 1]
                    ? dp[rol + 1][col]
                    : dp[rol + 1][col + 1] )
                    + triangle[rol][col];
            }
        }
        return dp[0][0];
    }
};

LeetCode 300 - Longest Increasing Subsequence - Medium/Hard

Given an unordered array of integers, find the length of the longest ascending subsequence.

Example:

Input: [10,9,2,5,3,7101,18]
Output: 4 
Explanation: the longest ascending subsequence is [2,3,7101], and its length is 4.

explain:
There may be multiple combinations of the longest ascending subsequences. You only need to output the corresponding length.
The time complexity of your algorithm should be O(n2).
Advanced: can you reduce the time complexity of the algorithm to O(nlogn) (Hard)?

If violence is enumerated, there are 2 n-power cases.

If the state is the optimal solution among the first i numbers, then return dp[n-1]. But in general, this is not the case. Please contact the largest sub paragraph and of question 53. For example, state derivation is not possible:

[1, 3, 2, 3, 1, 4]

dp[0]:1, [1]
dp[1]:2, [1, 3]
dp[2]:2, [1, 3], [1, 2]
dp[3]:3, [1, 2, 3]
dp[4]:3, [1, 2, 3]
dp[5]:4, [1, 2, 3, 4]

As in 53, state i represents the optimal solution ending with the ith number.

[1, 3, 2, 3, 1, 4]

dp[0]:1, [1]
dp[1]:2, [1, 3]
dp[2]:2, [1, 2]
dp[3]:3, [1, 2, 3]
dp[4]:1, [1]
dp[5]:4, [1, 2, 3, 4]

Solution 1

Four key elements are analyzed again:

  1. The original problem is the longest ascending subsequence; the subproblem is the longest ascending subsequence from the first 1 to n-1 numbers.
  2. Status. dp[i] can represent the optimal solution ending with the ith number.
  3. Boundary state. All are 1
  4. State transition. Before traversing i-1 solution, dp[i] is obtained.

The final result is the maximum value of the state array.

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int nSize = nums.size();
        if(nSize == 0) return 0;

        vector<int> dp(nSize, 1);
        dp[0] = 1;

        int nRes = dp[0];
        for(int i = 1; i < nSize; ++i)
        {
            for(int j = 0; j < i; ++j)
            {
                if( (nums[i] > nums[j] )
                    && (dp[j] + 1 > dp[i]) )
                {
                    dp[i] = dp[j] + 1;
                }
            }
            if( nRes < dp[i] ) nRes = dp[i];
        }

        return nRes;
    }
};

Solution 2

It's hard to think about using stack.

stack[i] represents the minimum possible value of the last element of the ascending sub segment with length i+1. To form the ascending sub sequence with length i+2, a number greater than stack[i] is required. The size of the final stack, that is, the optimal solution.

You can't use a stack container here, because it doesn't have an iterator, so you still use a vector.

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int nSize = nums.size();
        if(nSize == 0) return 0;

        vector<int> stack;
        stack.push_back(nums[0]);
        for(int i = 1; i < nSize; ++i)
        {
            if (nums[i] > stack.back())
            {
                stack.push_back(nums[i]);
            }
            else
            {
                int nStackSize = stack.size();
                for (int j = 0; j < nStackSize; ++j)
                {
                    if(stack[j] >= nums[i])
                    {
                        stack[j] = nums[i];
                        break;
                    }
                }
            }
        }

        return stack.size();
    }
};

Then the time complexity is optimized to nlogn by binary search.

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int nSize = nums.size();
        if(nSize == 0) return 0;

        vector<int> stack;
        stack.push_back(nums[0]);
        for(int i = 1; i < nSize; ++i)
        {
            if (nums[i] > stack.back())
            {
                stack.push_back(nums[i]);
            }
            else
            {
                auto it = lower_bound(stack.begin(), stack.end(), nums[i]);
                *it = nums[i];
            }
        }

        return stack.size();
    }
};

LeetCode 64 - Minimum Path Sum - minimum path sum

Given a m x n grid with nonnegative integers, find a path from the top left corner to the bottom right corner, so that the sum of the numbers on the path is the minimum.

Note: you can only move down or to the right one step at a time.

Example:

Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
 Explanation: because the sum of paths 1 → 3 → 1 → 1 → 1 is the smallest.

Direct solution:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int nRow = grid.size();
        if (nRow == 0) return 0;
        int nCol = grid[0].size();

        vector<vector<int>> dp;
        dp.push_back(vector<int>(nCol, 0));
        dp[0][0] = grid[0][0];
        for (int i = 1; i < nRow; ++i)
        {
            dp.push_back(vector<int>(nCol, 0));

            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int j = 1; j < nCol; ++j)
        {
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }


        for (int i = 1; i < nRow; ++i)
        {
            for (int j = 1; j < nCol; ++j)
            {
                dp[i][j] = (dp[i - 1][j] < dp[i][j - 1]
                        ? dp[i - 1][j]
                        : dp[i][j - 1])
                        + grid[i][j];
            }
        }

        return dp[nRow - 1][nCol - 1];
    }
};

LeetCode 174 - Dungeon Game - dungeon game - Hard

Some demons seized the princess (P) and locked her in the lower right corner of the dungeon. The underground city is a two-dimensional grid of M x N rooms. Our brave knight (K) was originally placed in a room in the upper left corner. He had to go through the dungeon and save the princess by fighting the demons.

The knight's initial health score is a positive integer. If his health score drops to zero or below at some point, he will die immediately.

Some rooms are guarded by demons, so the knight will lose health points when entering these rooms (if the value in the room is a negative integer, it means the knight will lose health points); other rooms are either empty (the value in the room is 0), or contain magic balls that increase the knight's health points (if the value in the room is a positive integer, it means the knight will increase health points).

In order to reach the princess as soon as possible, the knight decided to move only one step to the right or down at a time.

Write a function to calculate the minimum initial health points needed to ensure that the knight can save the princess.

For example, considering the following layout of the dungeon, if the knight follows the best path right - > right - > down - > down, then the initial health point of the knight is at least 7.

-2 (K)	-3	3
-5	-10	1
10	30	-5 (P)

explain:

There is no upper limit on health points for knights.

Any room may pose a threat to the knight's health points, and may also increase the knight's health points, including the upper left corner room entered by the knight and the lower right corner room where the princess is imprisoned.

The pitfall of this question is that if you recurs from top left to bottom right, you can't deduce the minimum amount of blood at the beginning, that is, you can't deduce the "at least" of the question.

If you push from the bottom right to the top left, each state represents how much hp you want to get here.

4 key elements analysis:

  1. For the original question, how much blood should be at least from the top left to the bottom right; for the sub question, how much blood should be at least (1+x) from [1~ m][1-n] to the bottom right
  2. Status. dp[i][j] represents the minimum amount of blood from here to the lower right corner.
  3. Boundary state. dp[m-1][n-1], compare 1 and 1-lower right corner, take the maximum value, and initialize the last row / column.
  4. State transition. Compare the minimum value of the right / lower dp lattice, and then compare with 1 to select the maximum value.

For example:

1*3
dungeon: 3, -5, 2
dp:
?, ?, 1 > 2 ? 1 : 2
?, 1 > 1-(-5) 1 : 1-(-5) , 1
1 > 6-3 ? 1 : 3, 6, 1
3, 6, 1
class Solution {
public:
    int calculateMinimumHP(vector<vector<int>>& dungeon) {
        int nRow = dungeon.size();
        if(nRow == 0) return 0;

        vector<vector<int>> dp;
        int nCol = dungeon[0].size();
        int nTmp = 0;

        for(int i = 0; i < nRow; ++i)
        {
            dp.push_back(vector<int>(nCol, 0));
        }

        nTmp = 1 - dungeon[nRow - 1][nCol - 1];
        dp[nRow - 1][nCol - 1] = 1 > nTmp ? 1 : nTmp;

        // Last line
        for( int i = nCol - 2; i >= 0; --i)
        {
            nTmp = dp[nRow - 1][i + 1] - dungeon[nRow - 1][i];
            dp[nRow - 1][i] = 1 > nTmp
                    ? 1
                    : nTmp;
        }
        // Last column
        for( int i = nRow - 2; i >= 0; --i )
        {
            nTmp = dp[i + 1][nCol - 1] - dungeon[i][nCol - 1];
            dp[i][nCol - 1] = 1 > nTmp
                    ? 1
                    : nTmp;
        }


        for(int i = nRow - 2; i >= 0; --i)
        {
            for(int j = nCol - 2; j >= 0; --j)
            {
                dp[i][j] = max({
                    1, 
                    // Right and bottom, which one needs less blood
                    min({
                        dp[i + 1][j] - dungeon[i][j],
                        dp[i][j + 1] - dungeon[i][j]
                    })
                });
            }
        }

        return dp[0][0];
    }
};

Tags: Programming REST less

Posted on Thu, 11 Jun 2020 04:02:28 -0400 by kryppienation