【leetcode真题】动态规划基础

什么是动态规划:

我就不做过多解释,这里推荐一篇文章给大家了解动态规划:动态规划

问题1:Climbing Stairs(斐波拉契问题)

方法:

  1. 定义数组元素的含义:定义 dp[i] 的含义,跳上 n 级的台阶总共由多少种跳法,那我们就定义 dp[i] 的含义为:跳上一个 i 级的台阶总共有 dp[i] 种跳法。这样,如果我们能够算出 dp[n],不就是我们要求的答案吗?所以第一步定义完成。
  2. 找出数组元素间的关系式:到达第 n 级的台阶有两种方式:一种是从第 n-1 级跳上来一种是从第 n-2 级跳上来由于我们是要算所有可能的跳法的,所以有== dp[n] = dp[n-1] + dp[n-2]。==
  3. 找出初始条件:当 n = 1 时,dp[1] = dp[0] + dp[-1],而我们是数组是不允许下标为负数的,所以对于 dp[1],我们必须要直接给出它的数值,相当于初始值,显然,dp[1] = 1。一样,dp[0] = 0.(因为 0 个台阶,那肯定是 0 种跳法了)。于是得出初始值:
    dp[0] = 0.
    dp[1] = 1.
    即 n <= 2 时,dp[n] = n.
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) {
        assert(n > 0);
        memo = vector<int>(n + 1, -1);
        return calcWays(n);
    }
};
问题2:Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
For example, given the following 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).

class Solution {
public:
    int minimumTotal(vector<vector<int>>& triangle) {
        for (int i = 1; i < triangle.size(); ++i) {
            for (int j = 0; j < triangle[i].size(); ++j) {
                if (j == 0) {
                    triangle[i][j] += triangle[i - 1][j];
                } else if (j == triangle[i].size() - 1) {
                    triangle[i][j] += triangle[i - 1][j - 1];
                } else {
                //因为每个结点能往下走的只有跟它相邻的两个数字,那么每个位置 (i, j) 也就只能从上层跟它相邻的两个位置过来,也就是 (i-1, j-1) 和 (i-1, j) 这两个位置
                    triangle[i][j] += min(triangle[i - 1][j - 1], triangle[i - 1][j]);
                }
            }
        }
        return *min_element(triangle.back().begin(), triangle.back().end());
    }
};
问题3: Minimum Path Sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.
Example:
Input:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
Output: 7

这道题给了我们一个只有非负数的二维数组,让找一条从左上到右下的路径,使得路径和最小,限定了每次只能向下或者向右移动。一个常见的错误解法就是每次走右边或下边数字中较小的那个,这样的贪婪算法获得的局部最优解不一定是全局最优解,因此是不行的。

/// Dynamic Programming
/// with O(n^2) space
///
/// Time Complexity: O(n^2)
/// Space Complexity: O(n^2)
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {

        int n = grid.size();
        assert(n > 0);

        int m = grid[0].size();
        assert(m > 0);

        vector<vector<int>> res = grid;

        //把第一行的值全变成路径的累加值
        for(int j = 1 ; j < m ; j ++)
            res[0][j] += res[0][j-1];

        //把第一列的值全变成路径的累加值
        for(int i = 1 ; i < n ; i ++)
            res[i][0] += res[i-1][0];

        //再把剩余的路径值变成全局的路径累加值
        for(int i = 1 ; i < n ; i ++)
            for(int j = 1 ; j < m ; j ++)
                res[i][j] += min(res[i-1][j], res[i][j-1]);

        return res[n-1][m-1];
    }
};
问题4:Integer Break

Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.
Example 1:
Input: 2
Output: 1
Explanation: 2 = 1 + 1, 1 × 1 = 1.
Example 2:
Input: 10
Output: 36
Explanation: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36.

自顶向下:

/// Memory Search
/// Time Complexity: O(n^2)
/// Space Complexity: O(n)
class Solution {
private:
    vector<int> memo;

    int max3(int a, int b, int c){
        return max(a, max(b, c));
    }

    /**
     * 将n进行分割(至少分割两部分),可以获得的最大乘积
     * @param n
     * @return
     */
    int 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 ++)
            //i + (n-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.clear();
        for(int i = 0 ; i < n + 1 ; i ++)
            memo.push_back(-1);
        return breakInteger(n);
    }
};

自底向上:

/// Dynamic Programming
/// Time Complexity: O(n^2)
/// Space Complexity: O(n)
class Solution {

private:
    int max3(int a, int b, int c ){
        return max(max(a, b), c);
    }

public:
    int integerBreak(int n) {

        assert(n >= 2);

        vector<int> memo(n + 1, -1);

        memo[1] = 1;
        for(int i = 2 ; i <= n ; i ++)
            for(int j = 1 ; j <= i - 1 ; j ++)
                memo[i] = max3(memo[i], j * (i - j), j * memo[i - j]);

        return memo[n];
    }
};
问题5:Perfect Squares
/// Memory Search
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {

public:
    int numSquares(int n) {

        vector<int> mem(n + 1, -1);

        return numSquares(n, mem);
    }

private:
    int numSquares(int n, vector<int>& mem){

        if(n == 0)
            return 0;

        if(mem[n] != -1)
            return mem[n];

        int res = INT_MAX;
        for(int i = 1; n - i * i >= 0; i ++ )
            //分割
            res = min(res, 1 + numSquares(n - i * i, mem));
        return mem[n] = res;
    }
};
问题5:Decode Ways

A message containing letters from A-Z is being encoded to numbers using the following mapping:
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
Given a non-empty string containing only digits, determine the total number of ways to decode it.
Example 1:
Input: “12”
Output: 2
Explanation: It could be decoded as “AB” (1 2) or “L” (12).
Example 2:
Input: “226”
Output: 3
Explanation: It could be decoded as “BZ” (2 26), “VF” (22 6), or “BBF” (2 2 6).

这道题要求解码方法,跟之前那道 Climbing Stairs 非常的相似,但是还有一些其他的限制条件,比如说一位数时不能为0,两位数不能大于 26,其十位上的数也不能为0,除去这些限制条件,跟爬梯子基本没啥区别,也勉强算特殊的斐波那契数列,当然需要用动态规划 Dynamci Programming 来解。建立一维 dp 数组,其中 dp[i] 表示s中前i个字符组成的子串的解码方法的个数,长度比输入数组长多多1,并将 dp[0] 初始化为1。现在来找状态转移方程,dp[i] 的值跟之前的状态有着千丝万缕的联系,就拿题目中的例子2来分析吧,当 i=1 时,对应s中的字符是 s[0]=‘2’,只有一种拆分方法,就是2,注意 s[0] 一定不能为0,这样的话无法拆分。当 i=2 时,对应s中的字符是 s[1]=‘2’,由于 s[1] 不为0,那么其可以被单独拆分出来,就可以在之前 dp[i-1] 的每种情况下都加上一个单独的2,这样 dp[i] 至少可以有跟 dp[i-1] 一样多的拆分情况,接下来还要看其能否跟前一个数字拼起来,若拼起来的两位数小于等于26,并且大于等于 10(因为两位数的高位不能是0),那么就可以在之前 dp[i-2] 的每种情况下都加上这个二位数,所以最终 dp[i] = dp[i-1] + dp[i-2],是不是发现跟斐波那契数列的性质吻合了。所以0是个很特殊的存在,若当前位置是0,则一定无法单独拆分出来,即不能加上 dp[i-1],就只能看否跟前一个数字组成大于等于 10 且小于等于 26 的数,能的话可以加上 dp[i-2],否则就只能保持为0了。具体的操作步骤是,在遍历的过程中,对每个数字首先判断其是否为0,若是则将 dp[i] 赋为0,若不是,赋上 dp[i-1] 的值,然后看数组前一位是否存在,如果存在且满足前一位是1,或者和当前位一起组成的两位数不大于 26,则当前 dp[i] 值加上 dp[i - 2]。最终返回 dp 数组的最后一个值即可,代码如下:

/// Dynamic Programming
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {

public:
    int numDecodings(string s) {

        int n = s.size();
        if(n == 1 || s[0] == '0') return s[0] != '0';

        vector<int> dp(n + 1, 0);
        dp[n] = 1;
        for(int i = n - 1; i >= 0; i --)
            if(s[i] != '0'){
                //dp保存最终结果,里面存着历史记录
                dp[i] = dp[i + 1];
                //如果第二个数和第一个数拼起来小于26
                if(i + 1 < n && s.substr(i, 2) <= "26") dp[i] += dp[i + 2];
            }

        return dp[0];
    }
};
问题6:Unique Paths 不同的路径

A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?

Above is a 7 x 3 grid. How many possible unique paths are there?
Note: m and n will be at most 100.
Example 1:
Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:

  1. Right -> Right -> Down
  2. Right -> Down -> Right
  3. Down -> Right -> Right

Example 2:
Input: m = 7, n = 3
Output: 28

/// Dynamic Programming
/// Time Complexity: O(m * n)
/// Space Complexity: O(m * n)
class Solution {
public:
    static int uniquePaths(int m, int n) {

        vector<vector<int>> dp(m, vector<int>(n, 1));
        //从第2行第2个元素开始,因为第一行和第一列已经被初始化了
        for(int i = 1; i < m; i ++)
            for(int j = 1; j < n; j ++)
                //从上面和左边更新数据,保证dp里面存的元素是最优子结构
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        return dp[m - 1][n - 1];
    }
};
问题6: Unique Paths II 不同的路径之二

**A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
Now consider if some obstacles are added to the grids. How many unique paths would there be?
An obstacle and empty space is marked as 1 and 0 respectively in the grid.
Note: m and n will be at most 100.
Example 1:
Input:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
Output: 2
Explanation:
There is one obstacle in the middle of the 3x3 grid above.
There are two ways to reach the bottom-right corner:

  1. Right -> Right -> Down -> Down
  2. Down -> Down -> Right -> Right**
/// Dynamic Programming
/// Time Complexity: O(m*n)
/// Space Complexity: O(m*n)
class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {

        int m = obstacleGrid.size();
        if(!m) return 0;

        int n = obstacleGrid[0].size();
        if(!n || obstacleGrid[0][0])
            return 0;

        vector<vector<long long>> dp(m, vector<long long>(n, 1ll));
        dp[0][0] = 1;
        //初始化障碍
        for(int j = 1; j < n; j ++)
            if(obstacleGrid[0][j])
                dp[0][j] = 0;
            else
                dp[0][j] = dp[0][j - 1];
        for(int i = 1; i < m; i ++)
            if(obstacleGrid[i][0])
                dp[i][0] = 0;
            else
                dp[i][0] = dp[i - 1][0];

        for(int i = 1; i < m; i ++)
            for(int j = 1; j < n; j ++)
                if(obstacleGrid[i][j])
                    dp[i][j] = 0;
                else
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
        return dp[m - 1][n - 1];
    }
};
问题6: Unique Paths II 不同的路径之二

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
Example 1:
Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.
Example 2:
Input: [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
Total amount you can rob = 2 + 9 + 1 = 12.

自顶向下:

/// Memory Search
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {
private:
    // meno[i]表示考虑抢劫nums[i.....n) 所能获得的最大收益
    vector<int> memo;

    int tryRob(const 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.clear();
        for(int i = 0 ; i < nums.size() ; i ++)
            memo.push_back(-1);
        return tryRob(nums, 0);
    }
};

自底向上:

/// Dynamic Programming
/// Time Complexity: O(n)
/// Space Complexity: O(n)
class Solution {

public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if (n == 0){
            return 0;
        }

        vector<int> memo(n, -1);
        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));
            }
        }

        return memo[0];
    }
};
问题6:House Robber II

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
Example 1:
Input: [2,3,2]
Output: 3
Explanation: You cannot rob house 1 (money = 2) and then rob house 3 (money = 2),
because they are adjacent houses.
Example 2:
Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.

/// Dynamic Programming
/// Two Pass House Robber I Problem
/// Time Complexity: O(n)
/// Space Complexity: O(1)
class Solution {
public:
    int rob(vector<int>& nums) {

        int n = nums.size();

        if(n == 0)
            return 0;

        if(n == 1)
            return nums[0];

        if(n == 2)
            return max(nums[0], nums[1]);

        /*
         * 现在房子排成了一个圆圈,
         * 则如果抢了第一家,
         * 就不能抢最后一家,因为首尾相连了,
         * 所以第一家和最后一家只能抢其中的一家,或者都不抢,
         * 那这里变通一下,如果把第一家和最后一家分别去掉,
         * 各算一遍能抢的最大值,然后比较两个值取其中较大的一个即为所求
         */

        return max(rob(nums, 0, nums.size() - 2), rob(nums, 1, nums.size() - 1));
    }

private:
    int rob(const vector<int>& nums, int start, int end){

        //因为数组是有限的,所以要在头部位置寻一个最大值出来
        int preMax = nums[start];
        int curMax = max(preMax, nums[start+1]);
        for(int i = start + 2 ; i <= end ; i ++){
            int temp = curMax;
            curMax = max(nums[i] + preMax, curMax);
            preMax = temp;
        }

        return curMax;
    }
};
问题6:House Robber III

The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the “root.” Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that “all houses in this place forms a binary tree”. It will automatically contact the police if two directly-linked houses were broken into on the same night.
Determine the maximum amount of money the thief can rob tonight without alerting the police.
Example 1:
Input: [3,2,3,null,3,null,1]
3
/
2 3
\ \
3 1
Output: 7
Explanation: Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.
Example 2:
Input: [3,4,5,1,3,null,1]
3
/
4 5
/ \ \
1 3 1
Output: 9
Explanation: Maximum amount of money the thief can rob = 4 + 5 = 9.

/// Brute Force
/// Time Complexity: O(2^n), where n is the nodes' number in the tree
/// Space Complexity: O(h), where h is the tree's height
class Solution {

private:
    map<pair<TreeNode*, int>, int> dp;

public:
    int rob(TreeNode* root) {

        dp.clear();
        return rob(root, true);
    }

private:
    int rob(TreeNode* root, bool include){

        if(root == NULL)
            return 0;

        //include记录改结点是否已被访问
        pair<TreeNode*, int> p = make_pair(root, include);
        if(dp.find(p) != dp.end())
            return dp[p];

        int res = rob(root->left, true) + rob(root->right, true);
        if(include)
            res = max(root->val + rob(root->left, false) + rob(root->right, false),
                       res);

        return dp[p] = res;
    }
};
问题7:0-1背包问题
class Knapsack01{

private:
    //记录该值是否被记录过,避免计算重复的情况
    vector<vector<int>> memo;

    //用[0...index]的物品,填充容积为c的背包的最大价值
    int bestValue(const vector<int> &w, const vector<int> &v,int index, int c){
        if (index < 0 || c <= 0){
            return 0;
        }
        //如果已经被记录过
        if (memo[index][c] != -1){
            return memo[index][c];
        }
        int res = bestValue(w,v,index-1,c);
        if (c >= w[index]){
            res = max(res, v[index] + bestValue(w,v,index-1,c-w[index]));
        }
        memo[index][c] = res;
        return res;
    }

public:
    int Knapsack01(const vector<int> &w, const vector<int> &v, int c){
        int n = w.size();
        memo = vector<vector<int>>(n, vector<int>(c+1, -1));
        return bestValue(w,v,n-1,c);
    }
};

自底向上:

class Knapsack01{

private:
    vector<vector<int>> memo;
public:
    int Knapsack01(const vector<int> &w, const vector<int> &v, int c){
        assert(w.size() == v.size());
        int n = w.size();
        memo = vector<vector<int>>(n, vector<int>(c+1, -1));
        if (n == 0){
            return 0;
        }
        for (int j = 0; j <= c ; j++) {
            memo[0][j] = (j>w[0] ? v[0]:0);
        }
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= c; j++) {
                memo[i][j] = memo[i-1][j];
                if (j > w[i]){
                    memo[i][j] = max(memo[i][j], v[i]+memo[i-1][j-w[i]]);
                }
            }
        }
        return memo[i-1][c];
    }
};

通过观察可以看到其实空间复杂度还可以优化的,因为实际用到的只有两行

class Knapsack01{

private:
    vector<vector<int>> memo;
public:
    int Knapsack01(const vector<int> &w, const vector<int> &v, int c){
        assert(w.size() == v.size());
        int n = w.size();
        memo = vector<vector<int>>(2, vector<int>(c+1, -1));
        if (n == 0){
            return 0;
        }
        for (int j = 0; j <= c ; j++) {
            memo[0][j] = (j>w[0] ? v[0]:0);
        }
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= c; j++) {
                memo[i%2][j] = memo[(i-1)%2][j];
                if (j > w[i]){
                    memo[i%2][j] = max(memo[i%2][j], v[i]+memo[(i-1)%2][j-w[i]]);
                }
            }
        }
        return memo[(i-1)%2][c];
    }
};

这时我们想想,能不能在一行上完成操作呢,是可以的,我们可以在一行上更新数据

class Knapsack01{

private:
    vector<vector<int>> memo;
public:
    int Knapsack01(const vector<int> &w, const vector<int> &v, int c){
        assert(w.size() == v.size());
        int n = w.size();
        memo = vector<int>(c+1, -1));
        if (n == 0){
            return 0;
        }
        for (int j = 0; j <= c ; j++) {
            memo[j] = (j>w[0] ? v[0]:0);
        }
        for (int i = 1; i < n; i++) {
            for (int j = c; j >= w[i=i]; j--) {
                memo[j] = max(memo[j], v[i]+memo[j-w[i]]);
                }
            }
        }
        return memo[c];
    }
};
问题8:Partition Equal Subset Sum 相同子集和分割
/// Dynamic Programming
/// Time Complexity: O(len(nums) * O(sum(nums)))
/// Space Complexity: O(len(nums) * O(sum(nums)))
class Solution {

public:
    bool canPartition(vector<int>& nums) {

        int sum = 0;
        for(int i = 0 ; i < nums.size() ; i ++){
            assert(nums[i] > 0);
            sum += nums[i];
        }

        if(sum % 2 != 0)
            return false;

        int n = nums.size();
        int C = sum / 2;
        //其中 memo[i] 表示原数组是否可以取出若干个数字,其和为i。
        vector<bool> memo(C + 1, false);
        //初始化 nums[i]的第一个数对应的memo[i]为 true
        for(int i = 0 ; i <= C ; i ++)
            memo[i] = (nums[0] == i);
        //遍历原数组中的数字
        for(int i = 1 ; i < n ; i ++)
            //第二个 for 循环一定要从 target 遍历到 nums[i],而不能反过来
            // 因为如果从 nums[i] 遍历到 target 的话,假如 nums[i]=1 的话,
            // 那么 [1, target] 中所有的 dp 值都是 true,因为 dp[0] 是 true,dp[1] 会或上 dp[0],
            // 为 true,dp[2] 会或上 dp[1],
            // 为 true,依此类推,完全使的 dp 数组失效了
            for(int j = C; j >= nums[i] ; j --)
                memo[j] = memo[j] || memo[j - nums[i]];

        return memo[C];
    }
};
问题9:Coin Change 硬币找零

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
Example 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)
Example 2:
coins = [2], amount = 3
return -1.
Note:
You may assume that you have an infinite number of each kind of coin.
Credits:
Special thanks to @jianchao.li.fighter for adding this problem and creating all test cases.

使用递归来暴力解决:

/// Memory Search
///
/// Time Complexity: O(coins_size * amount)
/// Space Complexity: O(amount)
class Solution {

private:
    vector<int> dp;
    int max_amount;

public:
    int coinChange(vector<int>& coins, int amount) {
        max_amount = amount + 1;
        dp = vector<int>(amount+1, -1);
        int res = search(coins, amount);
        return res == max_amount ? -1 : res;
    }

private:
    int search(const vector<int>& coins, int amount){

        if(amount == 0)
            return 0;

        if(dp[amount] != -1)
            return dp[amount];

        int res = max_amount;
        for(int coin: coins)
            if(amount - coin >= 0)
                res = min(res, 1 + search(coins, amount -coin));
        return dp[amount] = res;
    }
};

动态规划 Dynamic Programming 来做,好处是保留了一些中间状态的计算值,可以避免大量的重复计算。我们维护一个一维动态数组 dp,其中 dp[i] 表示钱数为i时的最小硬币数的找零,注意由于数组是从0开始的,所以要多申请一位,数组大小为 amount+1,这样最终结果就可以保存在 dp[amount] 中了。初始化 dp[0] = 0,因为目标值若为0时,就不需要硬币了。其他值可以初始化是 amount+1,为啥呢?因为最小的硬币是1,所以 amount 最多需要 amount 个硬币,amount+1 也就相当于当前的最大值了,注意这里不能用整型最大值来初始化,因为在后面的状态转移方程有加1的操作,有可能会溢出,除非你先减个1,这样还不如直接用 amount+1 舒服呢。好,接下来就是要找状态转移方程了,没思路?不要紧!回归例子1,假设我取了一个值为5的硬币,那么由于目标值是 11,所以是不是假如我们知道 dp[6],那么就知道了组成 11 的 dp 值了?所以更新 dp[i] 的方法就是遍历每个硬币,如果遍历到的硬币值小于i值(比如不能用值为5的硬币去更新 dp[3])时,用 dp[i - coins[j]] + 1 来更新 dp[i],所以状态转移方程为:
dp[i] = min(dp[i], dp[i - coins[j]] + 1);
其中 coins[j] 为第j个硬币,而 i - coins[j] 为钱数i减去其中一个硬币的值,剩余的钱数在 dp 数组中找到值,然后加1和当前 dp 数组中的值做比较,取较小的那个更新 dp 数组。先来看迭代的写法如下所示:

/// Dynamic Problem
/// 0-1 backpack problem
///
/// Time Complexity: O(coins_size * amount)
/// Space Complexity: O(amount)
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {

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

        for(int i = 1 ; i <= amount ; i ++)
            for(int coin: coins)
                if(i - coin >= 0)
                    dp[i] = min(dp[i], dp[i-coin] + 1);

        return dp[amount] == amount + 1 ? -1 : dp[amount];
    }
};
问题10:Combination Sum IV 组合之和之四

这道题的真正解法应该是用 DP 来做,这里需要一个一维数组 dp,其中 dp[i] 表示目标数为i的解的个数,然后从1遍历到 target,对于每一个数i,遍历 nums 数组,如果 i>=x, dp[i] += dp[i - x]。这个也很好理解,比如说对于 [1,2,3] 4,这个例子,当计算 dp[3] 的时候,3可以拆分为 1+x,而x即为 dp[2],3也可以拆分为 2+x,此时x为 dp[1],3同样可以拆为 3+x,此时x为 dp[0],把所有的情况加起来就是组成3的所有情况了,参见代码如下:

/// Dynamic Programming
/// Time Complexity: O(n * target)
/// Space Complexity: O(target)
class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {

        int n = nums.size();
        if(n == 0)
            return 0;

        vector<int> memo(target + 1, 0);
        memo[0] = 1;

        for(int i = 1; i <= target; i++)
            for(int j = 0; j < n; j ++)
                if(nums[j] <= i){
                    if(memo[i] == -1 || memo[i - nums[j]] == -1 ||
                       (long long)memo[i] + (long long)memo[i - nums[j]] > INT_MAX)
                        memo[i] = -1;
                    else memo[i] += memo[i - nums[j]];
                }
        assert(memo[target] != -1);
        return memo[target];
    }
};
问题10:最长上升子序列

示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

/// Dynamic Programming
/// Time Complexity: O(n^2)
/// Space Complexity: O(n)
class Solution {

public:
    int lengthOfLIS(vector<int>& nums) {

        if(nums.size() == 0)
            return 0;

        // memo[i] is the length of the longest increasing sequence end in nums[i]
        vector<int> memo(nums.size(), 1);
        for(int i = 1 ; i < nums.size() ; i ++)
            for(int j = 0 ; j < i ; j ++)
                if(nums[i] > nums[j])
                    memo[i] = max(memo[i], 1 + memo[j]);

        int res = memo[0];
        for(int i = 1 ; i < nums.size() ; i ++)
            res = max(res, memo[i]);

        return res;
    }
};

这里再推荐两道题道题:Wiggle Subsequence 摆动子序列
动态规划解最长公共子序列(LCS)

好的,至此,拖了一周的动态规划问题也总算解决了,临近开学前的一周,我把重心放回到了linux和SSM框架的学习上,力求在一周的时间内能做出一个完整的项目,但是结果却不尽人意,并没有很好地完成这个项目,还差前端那部分,还有在docker上部署项目的部分,但是时间还是有的,距离上网课还有一周的时间,在这最后一周里面,要尽可能利用时间了,这周最多只能去打两天球(周末),最后,对自己最近的表现还是挺满意的,总体来说比较自律,能按部就班学习,一定要保持下去,加油!!!!

你可能感兴趣的:(leetcode真题,动态规划,数据结构,算法)