leetcode经典题目(4)--动态规划(矩阵路径、子序列、背包问题、字符串编辑和股票问题等)

1. 斐波那契数列
1.1 爬楼梯(NO.70)

题目描述:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
解题思路:f(n)表示爬n阶台阶的方法数,可以先爬一节,后面为f(n-1),也可以先爬两阶,后面是f(n-2),所以f(n) = f(n-1)+f(n-2)。可以只用两个变量来存储f(n-1)和f(n-2),来减小空间复杂度。

class Solution {
public:
    int climbStairs(int n) {
        if (n <= 2)
            return n;
        int pre1 = 2, pre2 = 1;
        int cur = 0;
        for (int i = 3; i <= n; i++){
            cur = pre1 + pre2;
            pre2 = pre1;
            pre1 = cur;
        }
        return cur;
    }
};
1.2 强盗抢劫(NO.198)

题目描述:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
解题思路:也就是取数组中不相邻的数,使它们的和最大。动态规划关系式为:
d p [ i ] = m a x ( d p [ i − 2 ] + n u m s [ i ] , d p [ i − 1 ] ) dp[i]=max(dp[i-2]+nums[i],dp[i-1]) dp[i]=max(dp[i2]+nums[i],dp[i1])

class Solution {
public:
    int rob(vector<int>& nums) {
        int pre2 = 0, pre1 = 0;
        int cur = 0, max = 0;
        for (int i = 0; i < nums.size(); i++){
            cur = pre2+nums[i] > pre1 ? pre2+nums[i] : pre1;
            pre2 = pre1;
            pre1 = cur;
        }
        return cur;
    }
};
1.3 强盗在环形街区抢劫(NO.213)

题目描述:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
解题思路:求环形数组不相邻的数的最大和。转化为非环形的,一个是从第一个到倒数第二个,另一个是从第二个到最后一个,取两者的最大值。

class Solution {
public:
    int rob(vector<int>& nums) {
        int length = nums.size();
        if (length == 1)
            return nums[0];
        int num1 = rob2(nums,0,length-2);
        int num2 = rob2(nums,1,length-1);
        return num1 > num2 ? num1 : num2;
    }
    int rob2(vector<int>& nums, int start, int end){
        int pre2 = 0, pre1 = 0;
        for (int i = start; i <= end; i++){
            int cur = pre2 + nums[i] > pre1 ? pre2 + nums[i] : pre1;
            pre2 = pre1;
            pre1 = cur;
        }
        return pre1;
    }
};
1.4 信件错排

信封描述:某人写了n封信和n个信封,如果所有的信都装错了信封。求所有信都装错信封共有多少种不同情况。
解题思路:定义一个数组 dp 存储错误方式数量,dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:
(1)i==k,交换 i 和 j 的信后,它们的信和信封在正确的位置,但是其余 n-2 封信有 dp[n-2] 种错误装信的方式。由于 j 有 n-1 种取值,因此共有 (n-1)*dp[n-2] 种错误装信方式。
(2)i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 n-1 封信有 dp[n-1] 种错误装信方式。由于 j 有 n-1 种取值,因此共有 (n-1)*dp[n-1] 种错误装信方式。
在第二种情况中,可以将第j封信和第i个信封看做是一套,这样进行错排的话,第j封信不会出现在第i个信封中,所以变成了n-1错排问题。
所以,关系式为dp[n] = (n-1)(dp[n-1]+dp[n-2]),dp[1]=1,dp[2]=1.

2. 矩阵路径
2.1 矩阵的最小路径和(NO.64)

题目描述:给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
解题思路:dp[i][j]表示从左上角到第i行j列时路径数字的最小和,当i等于0时,dp[0][j]=dp[0][j-1]+grid[0][j];当j等于0时,dp[i][0] = dp[i-1][0] + grid[i][0];当i>0,j>0时,dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
       int row = grid.size(), col = grid[0].size();
       vector<vector<int>> dp(row,vector<int>(col,0));
       dp[0][0] = grid[0][0];
       for (int i = 1; i < row; i++)
            dp[i][0] = dp[i-1][0] + grid[i][0];
        for (int j = 1; j < col; j++)
            dp[0][j] = dp[0][j-1] + grid[0][j];
        for (int i = 1; i < row; i++){
            for (int j = 1; j < col; j++){
                int num = dp[i-1][j] < dp[i][j-1] ? dp[i-1][j] : dp[i][j-1];
                dp[i][j] = grid[i][j] + num;
            }
        }
        return dp[row-1][col-1];
    }
};
2.2 不同径数(NO.62)

题目描述:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?
解题思路:dp[i][j]表示从左上角走到第i行j列的路径数,当i为0或者j为0时,dp[i][j]=1;当i>1,j>1时,ij位置可由其上面或者左边的位置到达,所以dp[i][j]=dp[i-1][j]+dp[i][j-1]。
数学解法:总共要走m+n-2步,向下移动m-1次,向右移动n-1次, C m + n − 2 m − 1 C_{m+n-2}^{m-1} Cm+n2m1种方法。

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m,vector<int>(n,1));
        for (int i = 1; i < m; i++){
            for (int j = 1; j < n; j++)
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
        }
        return dp[m-1][n-1];
    }
};
3. 数组区间
3.1 数组区间和(NO.303)

题目描述:给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j,从0开始) 范围内元素的总和,包含 i, j 两点。你可以假设数组不可变。会多次调用 sumRange 方法。
解题思路:由于会多次调用函数,每次都对范围内的所有数进行相加是不明智的,所以将前i个数的和存放在sum的第i个位置,i到j范围内元素的和为sum[j+1]-sum[i]。

class NumArray {
public:
    vector<int> sum;
    NumArray(vector<int>& nums) {
        int length = nums.size();
        sum.push_back(0);
        for (int i = 0; i < length; i++){
            sum.push_back(sum[i] + nums[i]);
        }
    }   
    int sumRange(int i, int j) {
        return sum[j+1]-sum[i];
    }
};
3.2 数组中等差递增子区间的个数(NO.413)

题目描述:数组 A 包含 N 个数,且索引从0开始。函数要返回数组 A 中所有为等差数组的子数组个数。
解题思路:dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。当 A[i] - A[i-1] = A[i-1] - A[i-2],那么 [A[i-2], A[i-1], A[i]] 构成一个等差递增子区间。而且在以 A[i-1] 为结尾的递增子区间的后面再加上一个 A[i],一样可以构成新的递增子区间。所以当当A[i] - A[i-1] = A[i-1] - A[i-2]时,dp[i] = dp[i-1] + 1;如果不相等,dp[i]=0.

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) {
        int length = A.size();
        vector<int> dp(length,0);
        for (int i = 2; i < length; i++){
            if (A[i]- A[i-1] == A[i-1] - A[i-2])
                dp[i] = dp[i-1] + 1;
        }
        int total = 0;
        for (auto k : dp)
            total += k;
        return total;
    }
};
4. 分割整数
4.1 分割整数的最大乘积(NO.343)

题目描述:给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
解题思路:和剪绳子问题一样。尽可能多的剪长度为3的段,如果最后剩1,则和一个3变为两个2.


class Solution {
public:
    int integerBreak(int n) {
        if (n <= 2)
            return 1;
        if (n == 3)
            return 2;
        int count3 = n / 3;
        int count2 = 0;
        if (n % 3 == 1){
            count2 = 2;
            count3--;
        }
        if (n % 3 == 2)
            count2 = 1;
        return pow(3,count3)*pow(2,count2);
    }
};

动态规划解法:做出所有尝试,取最大值。

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1,0);
        dp[1] = 1;
        for (int i = 2; i <= n; i++){
            for (int j = 1; j < i; j++)
                dp[i] = max(dp[i],max(j*dp[i-j],j*(i-j)));
        }
        return dp[n];
    }
};
4.2 按平方数来分割整数(NO.279)

题目描述:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
解题思路:dp[i]表示组成正整数i的完全平方数的个数。对于一个小于i的完全平方数square,dp[i]=dp[i-square]+1;遍历所有小于i的平方数,找一个结果最小的。

class Solution {
public:
    
    int numSquares(int n) {
        vector<int> squareList = getSquareList(n);
        int length = squareList.size();
        vector<int> dp(n+1,n);
        dp[0] = 0;
        for (int i = 1; i <= n; i++){
            for (int square : squareList){
                if (square > i)
                    break;
                dp[i] = min(dp[i],dp[i-square]+1);
            }
        }
        return dp[n];
    }
    vector<int> getSquareList(int n){
        vector<int> squareList;
        for (int i = 1; i*i <= n; i++)
            squareList.push_back(i*i);
        return squareList;
    }
};
4.3 分割整数构成字母字符串(NO.91)

题目描述:一条包含字母 A-Z 的消息通过以下方式进行了编码:‘A’ -> 1;‘B’ -> 2;…;‘Z’ -> 26。给定一个只包含数字的非空字符串,请计算解码方法的总数。
例如:输入: “226”;输出: 3;解释: 它可以解码为 “BZ” (2 26), “VF” (22 6), 或者 “BBF” (2 2 6) 。
注意:单独的0不对应任何字母,所以不能分割出0,例如,“220”,只能分割为2和20,而不能分割为2,2,0.
解题思路:dp[i]表示以前i个字符的分割数量。有两种情况:(1)根据前i-1个字符的分割情况,再直接加入第i个字符,即将第i个字符看做一个分割段,当s[i]!=0时,dp[i]=dp[i]+dp[i-1];当s[i]等于0时,dp[i]不变。
(2)根据前i-2个字符的分割情况,将最后两个字符看做一个分割,这里需要最后两个字符表示的数大于等于10小于等于26.因为小于10时,和上面只考虑最后一个字符是一样的,此时dp[i]=dp[i]+dp[i-2]。

class Solution {
public:
    int numDecodings(string s) {
        int length = s.size();
        vector<int> dp(length+1,0);
        dp[0] = 1;
        dp[1] = s[0] == '0' ? 0 : 1;//第一个字符为0
        for (int i = 2; i <= length; i++){
            if (s[i-1] != '0')
                dp[i] += dp[i-1];
            int two = stoi(s.substr(i-2,2));
            if (two >= 10 && two <= 26)
                dp[i] += dp[i-2];
        }
        return dp[length];
    }
};
5. 最长递增子序列
5.1 最长上升子序列(NO.300)

题目描述:给定一个无序的整数数组,找到其中最长上升子序列的长度。
解题思路:令dp[i]表示以nums[i]结尾的数组的最长上升子序列的长度,若nums[i]大于等于其前面所有元素,则dp[i]等于1;若nums[j]小于nums[i],以nums[j]结尾的上升子序列加上nums[i]仍是上升子序列,所以dp[i]=dp[j]+1;找出所有j中的最大值即可。时间复杂度为 O ( N 2 ) O(N^2) O(N2).

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if (n == 0)
            return 0;
        vector<int> dp(n,1);
        int max = 1;
        for (int i = 1; i < n; i++){
            for (int j = 0; j < i; j++){
                if (nums[j] < nums[i])
                    dp[i] = dp[i] > dp[j]+1 ? dp[i] : dp[j] + 1;
            }
            if (max < dp[i])
                max = dp[i];
        }
        return max;
    }
};

解法二:定义一个 tails 数组,其中 tails[i-1](索引从0开始) 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x,如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1;如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x。 tails 数组保持有序,因此在查找 Si 位于 tails 数组的位置时就可以使用二分查找。
以[10,9,2,5,3,7,101,18]为例:

tailslen
101
91
21
2,52
2,32
2,3,73
2,3,7,1014
2,3,7,184
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if (nums.empty())
            return 0;
        vector<int> tails;
        tails.push_back(nums[0]);
        int len = 1;
        for (int i = 1; i < nums.size(); i++){
            int index = binarysearch(tails,len,nums[i]);
            if (index == len){
                tails.push_back(nums[i]);
                len++;
            }
            else{
                tails[index] = nums[i];
            }
        }
        return len;
    }
    int binarysearch(vector<int> nums, int length, int key){
        int l = 0, h = length - 1;
        while(l <= h){
            int m = l + (h - l) / 2;
            if (nums[m] == key)
                return m;
            else if (nums[m] < key)
                l = m + 1;
            else 
                h = m - 1;
        }
        return l;
    }
};
5.2 一组整数对能够构成的最长链(NO.646)

题目描述:给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。
解题思路:由于是任意顺序选择,所以先进行排序,根据数对的第一个值从小到大进行排序。令dp[i-1]表示前i个数对所能形成的数对链的长度,如果存在j<i,pairs[j][1]<pairs[i][0],则bp[i]=bp[j]+1;否则,bp[i]=1.需要两层循环,复杂度为 O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b){
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] < b[0];
    }
    int findLongestChain(vector<vector<int>>& pairs) {
        int n = pairs.size();
        if (n == 0)
            return 0;
        //排序
        sort(pairs.begin(),pairs.end(),cmp);
        vector<int> dp(n,1);
        int max = 1;
        for (int i = 1; i < n; i++){
            for (int j = 0; j < i; j++){
                if (pairs[i][0] > pairs[j][1])
                    dp[i] = dp[i] > dp[j] + 1 ? dp[i] : dp[j] + 1;
            }
            if (max < dp[i])
                max = dp[i];
        }
        return max;
    }
};

解法二:贪心算法。根据数对的第二个值进行从小到大排序,再进行遍历,如果当前数对可以加入链,则加入。时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度为 O ( n ) O(n) O(n)

class Solution {
public:
    static bool cmp(vector<int>& a, vector<int>& b){
        if (a[1] == b[1]) return a[0] < b[0];
        return a[1] < b[1];
    }
    int findLongestChain(vector<vector<int>>& pairs) {
        int n = pairs.size();
        if (n == 0)
            return 0;
        //排序
        sort(pairs.begin(),pairs.end(),cmp);
        int res = 1, tmp = pairs[0][1];
        for (int i = 1; i < n; i++){
            if (pairs[i][0] > tmp){
                res++;
                tmp = pairs[i][1];
            }
        }
        return res;
    }
};
5.3 最长摆动子序列(NO.376)

题目描述:如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
解题思路:对于数组中连续重复的元素,只保留一个。如果nums[i]-nums[i-1]与nums[i-1]-nums[i-2]异号,则dp[i]=dp[i-1]+1,否则dp[i]=dp[i-1]

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.empty())
            return 0;
        int i = 1;
        while (i < nums.size()){//删除数组中的连续重复的元素,只保留一个
            if (nums[i] == nums[i-1])
                nums.erase(nums.begin()+i);
            else
                i++;
        }
        int n = nums.size();
        if (n <= 2)
            return n;
        vector<int> dp(n,0);
        dp[0] = 1; dp[1] = 2;
        for (int i = 2; i < n; i++){
            if ((nums[i] - nums[i-1]) * (nums[i-1] - nums[i-2]) < 0)
                dp[i] = dp[i-1] + 1;
            else 
                dp[i] = dp[i-1];
        }
        return dp[n-1];
    }
};

解法二:如果连续上升或者下降,up与down的值是不变的。

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if (n == 0)
            return 0;
        int up = 1, down = 1;
        for (int i = 1; i < nums.size(); i++){
            if (nums[i] - nums[i-1] > 0)
                up = down +1;
            else if (nums[i] - nums[i-1] < 0)
                down = up + 1;
        }
        return max(up,down);
    }
};
6. 最长公共子序列

题目描述:给定两个字符串S1 和S2,返回这两个字符串的最长公共子序列的长度。一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
解题思路:dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况:
(1)当 S 1 i = = S 2 j S1_i == S2_j S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S 1 i S1_i S1i 这个值,最长公共子序列长度加 1,即 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i1][j1]+1
(2)当 S 1 i ! = S 2 j S1_i != S2_j S1i!=S2j时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,或者 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,取它们的最大者,即 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = max( dp[i-1][j], dp[i][j-1] ) dp[i][j]=max(dp[i1][j],dp[i][j1])

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n1 = text1.size(), n2 = text2.size();
        vector<vector<int>> dp(n1+1,vector<int>(n2+1,0));
        for (int i = 1; i <= n1; i++){
            for (int j =1; j <= n2; j++){
                if (text1[i-1] == text2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
            }
        }
        return dp[n1][n2];
    }
};
7. 0-1背包问题

0-1背包问题
有一个容量为 N 的背包,要用这个背包装下物品的价值最大,每个物品只能使用一次。这些物品有两个属性:体积 w 和价值 v。(其实,背包就是一个约束条件,求在该条件下的最值问题)
定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
(1)第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。
(2)第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w ] + v ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v) dp[i][j]=max(dp[i1][j],dp[i1][jw]+v)(想不通的话,可以举例,手写一遍便可以找出规律)

int knapsack(int W, int N, vector<int> weights, vector<int> values){
    //W为背包总体积,N为物品数量,weights存储物品体积,values存储物品价格
    vector<vector<int>> dp(N+1,vector<int>(W+1,0));
    for (int i = 1; i <= N; i++){
        int w = weights[i], v = values[i];
        for (int j = 1; j <= W; j++){
            if (j >= w)//可以将第i个物品放入背包,也可以不放,取两者的最大值
                dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w] + v);
            else//背包空间不足以放下第i个物品
                dp[i][j] = dp[i-1][j];
        }
    }
    return dp[N][W];
}

观察状态转移方程可以知道,前 i 件物品的状态仅与前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,dp[j]表示体积不超过j时的最大价值。状态转移方程为:
d p [ j ] = m a x ( d p [ j ] , d p [ j − w ] + v ) dp[j]=max(dp[j],dp[j-w]+v) dp[j]=max(dp[j],dp[jw]+v).
注意,在更新dp时,要从后往前更新,先更新j较大的,让j递减,否则更新后的值覆盖原值,影响后面的更新。

int knapsack(int W, int N, vector<int> weights, vector<int> values){
    //W为背包总体积,N为物品数量,weights存储物品体积,values存储物品价格
    vector<int> dp(W+1,0);
    for (int i = 1; i <= N; i++){
        int w = weights[i-1], v = values[i-1];
        for (int j = W; j >= 1; j--){//从后往前更新
            if (j >= w)
                dp[j] = max(dp[j],dp[j-w]+v);
        }
    }
    return dp[W];
}

常见的背包问题有1、组合问题。2、True、False问题。3、最大最小问题。

7.1 划分数组为和相等的两部分(NO.416)

题目描述:给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。注意: 每个数组中的元素不会超过 100数组的大小不会超过 200。
解题思路:其实就是能否从数组中选择一些元素使其和为给定值。可以看成一个背包大小为 sum/2 的 0-1 背包问题,数组中元素表示体积,看能否选择一些物品正好将背包装满。
dp[i][j]表示:前i个元素中是否存在一些元素,使这些元素的和为j。若j大于等于第i个数,则可以考虑是否将第i个数放进来,所以 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] ∣ ∣ d p [ i − 1 ] [ j − n u m s [ i − 1 ] ] ; dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]]; dp[i][j]=dp[i1][j]dp[i1][jnums[i1]];否则,第i个数不能放进来,则 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]。(可以以[1,5,11,5]为例写出动态矩阵)

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        int sum = 0;
        for (int e : nums)
            sum += e;
        if (sum % 2 == 1)
            return false;
        int m = sum / 2;
        vector<vector<bool>> dp(n+1,vector<bool>(m+1,false));
        dp[0][0] = true;
        for (int i = 1; i <= n; i++){
            dp[i][0] = true;
            for (int j = 1; j <= m; j++){
                if (j >= nums[i-1])
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
                else
                    dp[i][j] = dp[i-1][j];
            }
        }
        return dp[n][m];
    }
};

使用一维数组进行空间优化。注意,只对j >= nums[i-1]改变dp[j],前面的那些元素与上一次dp中的对应值是一样的,不需要改变。

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        int sum = 0;
        for (int e : nums)
            sum += e;
        if (sum % 2 == 1)
            return false;
        int m = sum / 2;
        vector<bool> dp(m+1,false);
        dp[0] = true;
        for (int i = 1; i <= n; i++){
            for (int j = m; j >= nums[i-1]; j--){
                //nums[i-1]之前的dp元素与原来一样
                dp[j] = dp[j] || dp[j-nums[i-1]];
            }
        }
        return dp[m];
    }
};
7.2 改变一组数的正负号使得它们的和为一给定数(NO.494)

题目描述:给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
解题思路:该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
s u m ( P ) − s u m ( N ) = t a r g e t ; sum(P) - sum(N) = target; sum(P)sum(N)=target;
s u m ( P ) + s u m ( N ) + s u m ( P ) − s u m ( N ) = t a r g e t + s u m ( P ) + s u m ( N ) ; sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N); sum(P)+sum(N)+sum(P)sum(N)=target+sum(P)+sum(N);
2 ∗ s u m ( P ) = t a r g e t + s u m ( n u m s ) 2 * sum(P) = target + sum(nums) 2sum(P)=target+sum(nums)
因此只要找到一个子集,令它们都取正号,并且和等于 (target + sum(nums))/2,就证明存在解。
本题与上一题的解法思想一模一样,唯一不同的点在于上一题是判断能否找到一些元素使其和为给定值,而这一题是要找有多少种选择。
dp[j]表示和为j的可选方法数。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        int n = nums.size();
        int sum = 0;
        for (int e : nums)
            sum += e;
        if (sum < S || (sum + S) % 2 == 1)
            return 0;
        int m = (sum + S) / 2;
        vector<int> dp(m+1,0);
        dp[0] = 1;
        for (int i = 0; i < n; i++){
            for (int j = m; j >= nums[i]; j--){
                dp[j] = dp[j] + dp[j - nums[i]];//计算方法数
            }
        }
        return dp[m];
    }
};
7.3 01 字符构成最多的字符串(NO.474)

题目描述:,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。
输入: Array = {“10”, “0001”, “111001”, “1”, “0”}, m = 5, n = 3
输出: 4; 解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 “10”,“0001”,“1”,“0” 。
解题思路:比最基本的01背包多了一个需要考虑的变量,但动态规划的核心还是不变的。使用三维数组。dp[i][j][k]表示对前i个字符串,使用j个0和k个1能拼出的最大数量。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int sz = strs.size();
        vector<vector<vector<int>>> dp(sz+1,vector<vector<int>>(m+1,vector<int>(n+1,0)));
        for (int i = 1; i <= sz; i++){
            int zeros = 0, ones = 0;
            for (auto c : strs[i-1]){
                if (c == '0')
                    zeros++;
                else 
                    ones++;
            }
            for (int j = 0; j <= m; j++){
                for (int k = 0; k <= n; k++){
                    if (j >= zeros && k >= ones)//需要考虑是否加入第i个字符串
                        dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-zeros][k-ones]+1);
                    else
                        dp[i][j][k] = dp[i-1][j][k];
                }
            }
        }
        return dp[sz][m][n];
    }
};

空间优化,使用二维数组。dp[j][k]表示使用j个1k个0所能拼出的最大数量。

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int sz = strs.size();
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        for (int i = 1; i <= sz; i++){
            int zeros = 0, ones = 0;
            for (auto c : strs[i-1]){
                if (c == '0')
                    zeros++;
                else 
                    ones++;
            }
            for (int j = m; j >= 0; j--){
                for (int k = n; k >= 0; k--){
                    if (j >= zeros && k >= ones)//需要考虑是否加入第i个字符串
                        dp[j][k] = max(dp[j][k],dp[j-zeros][k-ones]+1);
                }
            }
        }
        return dp[m][n];
    }
};

完全背包问题
完全背包问题与0-1背包问题的区别在于每一件物品的数量都有无限个,而0-1背包每件物品数量只有一个。
有编号分别为a,b,c,d的四件物品,它们的重量分别是2,3,4,7,它们的价值分别是1,3,5,9,每件物品数量无限个,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
初始化时,当只考虑一件物品a时, d p [ 1 ] [ j ] = j / w e i g h t [ a ] dp[1][j] = j/weight[a] dp[1][j]=j/weight[a]
递推公式计算时, d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] ) dp[i][j] = max(dp[i-1][j], dp[i][j-weight[i]]+value[i]) dp[i][j]=max(dp[i1][j],dp[i][jweight[i]]+value[i]),注意这里当考虑放入一个物品 i 时应当考虑还可能继续放入 i,因此这里是 d p [ i ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] dp[i][j-weight[i]]+value[i] dp[i][jweight[i]]+value[i], 而不是 d p [ i − 1 ] [ j − w e i g h t [ i ] ] + v a l u e [ i ] dp[i-1][j-weight[i]]+value[i] dp[i1][jweight[i]]+value[i]

012345678910
i=000000000000
i=100112233445
i=200133466799
i=300135568101011
i=400135569101012

可以删除i=0的那一行,直接对i=1的行进行初始化。

7.4 找零钱的最少硬币数(NO.322)

题目描述:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。可以认为每种硬币的数量是无限的。
解题思路:类似于完全背包问题,每个物品都可以无限使用,但是要求背包必须装满,而且要求背包中的物品数目最少。dp[i][j]表示使用前i种硬币总金额为j时的最少数量。在初始化时,我们让dp所有的值都为amount+1。当j>=coin[i-1],可以考虑是否使用第i中硬币,此时 d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − c o i n [ i − 1 ] ] + 1 ) dp[i][j]=min(dp[i-1][j],dp[i][j-coin[i-1]]+1) dp[i][j]=min(dp[i1][j],dp[i][jcoin[i1]]+1),否则, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]
使用一维数组进行空间优化。 d p [ j ] = m i n ( d p [ j ] , d p [ j − c o i n [ i − 1 ] ] + 1 ) dp[j]=min(dp[j],dp[j-coin[i-1]]+1) dp[j]=min(dp[j],dp[jcoin[i1]]+1)

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size();
        if (n == 0)
            return -1;
        vector<vector<int>> dp(n+1,vector<int>(amount+1,amount+1));
        for (int i = 1; i <= n; i++){
            dp[i][0] = 0;
            for (int j = 1; j <= amount; j++){
                if (j >= coins[i-1])
                    dp[i][j] = min(dp[i-1][j],dp[i][j-coins[i-1]]+1);
                else
                    dp[i][j] = dp[i-1][j];
            }
        }
        return dp[n][amount] > amount ? -1 : dp[n][amount];
    }
};

使用一维数组。注意,与01背包问题不同,这里在更新dp时,要从前往后更新,因为更新后面的值时要用到前面的值。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n = coins.size();
        if (n == 0)
            return -1;
        vector<int> dp(amount+1,amount+1);
        for (int i = 1; i <= n; i++){
            dp[0] = 0;
            for (int j = 1; j <= amount; j++){//从前往后更新
                if (j >= coins[i-1])
                    dp[j] = min(dp[j],dp[j-coins[i-1]]+1);
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
};
7.5 找零钱的硬币数组合(NO.518)

题目描述:给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
解题思路:dp[i][j]表示使用前i中硬币组成金额j的种数。假设第i种硬币面额为coin,当j大于等于coin,此时就可以考虑是否使用第i种硬币,不使用第i种硬币的组合数为dp[i-1][j],使用第i种硬币的组合数为dp[i][j-coin](硬币可以重复使用),所以状态转移矩阵为:
当j>=coin时, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − c o i n ] dp[i][j]=dp[i-1][j]+dp[i][j-coin] dp[i][j]=dp[i1][j]+dp[i][jcoin];否则, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int n = coins.size();
        vector<vector<int>> dp(n+1,vector<int>(amount+1,0));
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++){
            dp[i][0] = 1;
            for (int j = 1; j <= amount; j++){
                if (j >= coins[i-1])
                    dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]];
                else
                    dp[i][j] = dp[i-1][j];
            }
        }
        return dp[n][amount];
    }
};

使用一维数组。 d p [ j ] = d p [ j ] + d p [ j − c o i n ] dp[j] = dp[j] + dp[j-coin] dp[j]=dp[j]+dp[jcoin]

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int n = coins.size();
        vector<int> dp(amount+1,0);
        dp[0] = 1;
        for (int coin : coins){
            for (int j = 1; j <= amount; j++){
                if (j >= coin)
                    dp[j] = dp[j] + dp[j-coin];
            }
        }
        return dp[amount];
    }
};
7.6 字符串按单词列表分割(NO.139)

题目描述:给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。拆分时可以重复使用字典中的单词。可以假设字典中没有重复的单词。
解题思路:dp[i]表示s的前i个字符能否被拆分。在判断dp[i]时,需要遍历字典中的所有单词,若某个单词的长度小于i,则考虑substr(i-len,len)是否等于该单词,以及dp[i-len]的值。

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        int n = s.size();
        vector<bool> dp(n+1,false);
        dp[0] = true;
        for (int i = 1; i <= n; i++){
            for (string word : wordDict){
                int len = word.size();
                if (i >= len && s.substr(i-len,len) == word)
                    dp[i] = dp[i] || dp[i-len];
            }
        }
        return dp[n];
    }
};
7.7 组合总和(NO.377)

题目描述:给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。顺序不同的序列被视作不同的组合。
解题思路:本题与7.5的唯一区别在于本题要考虑组合的顺序。而只要换掉内循环和外循环的位置,就是两个问题的解题关键。dp[i]表示和为i的方法数。遍历数组中的所有数num,如果num<=i,则可以考虑加入num。例如:数组为[1,2,3],target=4,dp[4]便可以分解为1与dp[3],2与dp[2],3与dp[1]。状态转移公式为: d p [ i ] + = d p [ i − n u m ] dp[i] += dp[i-num] dp[i]+=dp[inum].

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<unsigned long long> dp(target+1,0);
        dp[0] = 1;
        for (int i = 1; i <= target; i++){
            for (int num : nums){
                if (i >= num)
                    dp[i] += dp[i-num];
            }
        }
        return dp[target];
    }
};
8. 字符串编辑
8.1 两个字符串的最长公共子序列(NO.583)

题目描述:给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
解题思路:转化求这两个单词的最长公共子序列,只要修改公共子序列之外的字符即可。dp[i][j]表示word1的前i个字符与word2的前j个字符的最长公共子序列。如果word[i-1]==word2[j-1],则 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i1][j1]+1;否则, d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j]=max(dp[i-1][j],dp[i][j-1]) dp[i][j]=max(dp[i1][j],dp[i][j1])

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size(), len2 = word2.size();
        vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
        for (int i =1; i <= len1; i++){
            for (int j = 1; j <= len2; j++){
                if (word1[i-1] == word2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
            }
        }
        return len1 + len2 - 2 * dp[len1][len2];//一个单词为空时,返回另一个的长度
    }
};
8.2 编辑距离(NO.72)

题目描述:给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:插入一个字符;删除一个字符;替换一个字符。
解题思路:dp[i][j]表示将Word1的前i个字符编辑为Word2的前j个字符的最小编辑代价。
如果 w o r d [ i − 1 ] = = w o r d [ j − 1 ] word[i-1]==word[j-1] word[i1]==word[j1],则 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j-1] dp[i][j]=dp[i1][j1];
如果不相等,可以先将word1的前i-1个字符编辑为word2的前j个字符,然后删除word1的第i个字符,或者先将word1的前i个字符编辑为word2的前j-1个字符,再在word1上添加一个字符,或者先将word1的前i-1个字符编辑为word2的前j-1个字符,然后对第i个字符进行替换,取这三种操作的最小数,即 d p [ i ] [ j ] = m i n ( d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j − 1 ] + 1 ) dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1) dp[i][j]=min(dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1).

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.size(), len2 = word2.size();
        vector<vector<int>> dp(len1+1,vector<int>(len2+1,0));
        for (int i = 1; i <= len1; i++)
            dp[i][0] = i;
        for (int j = 1; j <= len2; j++)
            dp[0][j] = j;
        for (int i = 1; i <= len1; i++){
            for (int j = 1; j <= len2; j++){
                if (word1[i-1] == word2[j-1])
                    dp[i][j] = dp[i-1][j-1];
                else
                    dp[i][j] = min(dp[i-1][j],min(dp[i][j-1],dp[i-1][j-1])) + 1;
            }
        }
        return dp[len1][len2];
    }
};
8.3 复制粘贴字符(NO.650)

题目描述:最初在一个记事本上只有一个字符 ‘A’。你每次可以对这个记事本进行两种操作:
Copy All (复制全部) : 你可以复制这个记事本中的所有字符(部分的复制是不允许的)。
Paste (粘贴) : 你可以粘贴你上一次复制的字符。
给定一个数字 n 。你需要使用最少的操作次数,在记事本中打印出恰好 n 个 ‘A’。输出能够打印出 n 个 ‘A’ 的最少操作次数。
解题思路:1、如果n为一个质数,那么结果就是n,因为只能一个个粘贴。2、如果n为一个合数,那么他的结果就是分解因式的结果之和,比如n 为8 ,
那么dp[8] = dp[4]+dp[2]; 因为,8 = 42
dp[4] = dp[2]+dp[2]; 因为,4 = 2
2
由于因为2是一个质数,dp[2] = 2;
于是dp[8] = 6就是我们要求的结果。

class Solution {
public:
    int minSteps(int n) {
        vector<int> dp(n+1,0);
        for (int i = 2; i <= n; i++){
            dp[i] = i;
            for (int j = 2; j <= sqrt(i); j++){
                if (i % j == 0)
                    dp[i] = dp[j] + dp[i / j];
            }
        }
        return dp[n];
    }
};
8.4 最长不含重复字符的子字符串(剑指 Offer 48)

题目描述:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
解题思路:dp[i]表示以第i个字符结尾的字符串的最长不重复子串的长度。如果第i个字符之前没有出现过,那么dp[i]=dp[i-1]+1;如果出现过,我们使用k表示上次出现的位置,d=i-k为两者之间的距离,如果d<=dp[i-1],说明该字符上次出现在dp[i-1]对应的最长子字符串中,所以dp[i]=d;如果d>dp[i-1],dp[i]=dp[i-1]+1。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.size();
        if (n == 0)
            return 0;
        vector<int> dp(n,1);
        int max = 1;
        for (int i = 1; i < n; i++){
            int k = i - 1;
            while (k >= 0 && s[k] != s[i])
                k--;
            if (k >= 0 && i - k <= dp[i-1])
                dp[i] = i - k;
            else
                dp[i] = dp[i-1] + 1; 
            if (max < dp[i])
                max = dp[i];
        }
        return max;
    }
};
9. 股票交易
9.1 需要冷却期的股票交易(NO.309)

题目描述:给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
解题思路:动态规划的思想:
sell[i],表示该天结束后手里没有股票的情况下,已经获得的最大收益。sell[i],表示手里没有股票的收益,这种可能性是今天卖了,或者啥也没干。今天啥也没干那就是sell[i]=sell[i-1],今天卖了,那么sell[i]就是前一天有股票的收益加上今天卖出去股票的价格。sell[i]=buy[i-1]+price[i].因此: s e l l [ i ] = m a x ( s e l l [ i − 1 ] , b u y [ i − 1 ] + p r i c e [ i ] ) sell[i]=max(sell[i-1],buy[i-1]+price[i]) sell[i]=max(sell[i1],buy[i1]+price[i])
buy[i],表示该天结束后手里有股票的情况下,已经获得的最大收益。buy[i],表示今天手里有股票的收益,这种可能性是今天买了股票或者啥也没干。今天啥也没干那就是buy[i]=buy[i-1],今天买了股票,那么buy[i]=cool[i-1]-price[i].因此:
b u y [ i ] = m a x ( b u y [ i − 1 ] , c o o l [ i − 1 ] − p r i c e [ i ] ) buy[i]=max(buy[i-1],cool[i-1]-price[i]) buy[i]=max(buy[i1],cool[i1]price[i])
cool[i],表示该天是冷冻期,说明在i-1天卖掉了股票,那么i天的收益和i-1天是一样的,则cool[i]=sell[i-1]。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n == 0)
            return 0;
        vector<int> sell(n+1,0);
        vector<int> buy(n+1,0);
        vector<int> cool(n+1,0);
        buy[1] = -prices[0];
        for (int i = 2; i <= n; i++){
            sell[i] = max(buy[i-1]+prices[i-1],sell[i-1]);
            buy[i] = max(cool[i-1]-prices[i-1],buy[i-1]);
            cool[i] = sell[i-1];
        }
        return sell[n];
    }
};
9.2 需要交易费用的股票交易(NO.714)

题目描述:给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费,也就是在卖出额时候交手续费就行。
解题思路:与上一题相比,本题没有冷冻期,但是要交易费。
sell[i],表示手里没有股票的收益,这种可能性是今天卖了,或者啥也没干。今天啥也没干那就是sell[i]=sell[i-1],今天卖了,那么sell[i]就是前一天有股票的收益加上今天卖出去股票的价格并减去交易费。sell[i]=hold[i-1]+price[i]-fee.因此:
s e l l [ i ] = m a x ( s e l l [ i − 1 ] , b u y [ i − 1 ] + p r i c e [ i ] − f e e ) sell[i]=max(sell[i-1],buy[i-1]+price[i]-fee) sell[i]=max(sell[i1],buy[i1]+price[i]fee)
buy[i],表示今天手里有股票的收益,这种可能性是今天买了股票或者啥也没干。今天啥也没干那就是buy[i]=buy[i-1],今天买了股票,那么buy[i]=selll[i-1]-price[i].因此:
b u y [ i ] = m a x ( b u y [ i − 1 ] , s e l l [ i − 1 ] − p r i c e [ i ] ) buy[i]=max(buy[i-1],sell[i-1]-price[i]) buy[i]=max(buy[i1],sell[i1]price[i])

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        if (n == 0)
            return 0;
        vector<int> sell(n+1,0);
        vector<int> buy(n+1,0);
        buy[1] = -prices[0];
        for (int i = 2; i <= n; i++){
            sell[i] = max(buy[i-1]+prices[i-1]-fee,sell[i-1]);//卖出时要付交易费
            buy[i] = max(sell[i-1]-prices[i-1],buy[i-1]);
        }
        return sell[n];
    }
};

应为sell[i],buy[i]仅与sell[i-1],buy[i-1]有关,所以可以不用数组存储,而只用两个变量sell和buy。

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        if (n == 0)
            return 0;
        int sell = 0, buy = -prices[0];
        for (int i = 2; i <=n; i++){
            sell = max(sell,buy+prices[i-1]-fee);
            buy = max(buy,sell-prices[i-1]);
        }
        return sell;
    }
};
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值