【LeetCode 刷题笔记】 02 LeetCode 热题 HOT 100 中的动态规划

博客都快被荒废了,今天才突然想起还有这么个东西,决定将自己最近刷到的LeetCode热题榜单中的动态规划的内容总结一下,与大家分享。

干货可能比较多。本人有点懒,就不一题一题地发博客了XD

LeetCode 算法100题总结-dp

To be yourself in a world that is constantly trying to make you something else is the greatest accomplishment.

——Ralph W. Emerson

动态规划

由于是动态规划专题,所以有的题目可能有更好的解法没有被写在里面,这篇博客只聚焦于动态规划解法。

有几种常见的一维dp数组,第一种是记录前 i 个元素【中】(最大/最长的)长度,第二种是记录以第 i 个元素【为结尾】的(最长)长度,第三种是记录前 i 个元素在某种性质上的 true 和 false

动态规划有单状态和多状态的

多状态dp要注意同时更新的问题,即要先将所有状态储存在临时变量中,再用临时变量对所有状态进行更新

多状态:

152题(最大和最小)

5. Longest Palindromic Substring

dp[i][j] 表示从下标 i 到下标 j 的子串是否是回文串

关键:

  1. 递推式?
  2. 二维数组的斜向遍历如何写?(可以记模板,实在不行就把下标写出来然后找规律)

时间和空间:O(n2)

代码:

class Solution {
public:
    string longestPalindrome(string s) {
        int l = s.size();
        if (l == 0 || l == 1)
            return s;
        vector<vector<bool>> dp(l, vector<bool>(l, false));
        for (int i = 0; i < l; i++) {
            dp[i][i] = true;
            if (i > 0)
                dp[i][i-1] = true;
        }
        int i, j;
        int start = 0, end = 0;
        // 关键!!!
        for (int k = 1; k < l; k++) {
            for (i = 0; i < l-k; i++) {
                j = i+k;
                if (dp[i+1][j-1] && s[i] == s[j]) {
                    dp[i][j] = true;
                    start = i;
                    end = j;
                }
                else
                    dp[i][j] = false;
            }
        }
        return s.substr(start, end-start+1);
    }
};

10. Regular Expression Matching

  • 正则表达式匹配,应该要用到前面的状态来推出当前状态会比较省时间 ---> 动态规划
  • 要理解题意,模板 ba* 可以匹配字符串 b

递推式(分3种情况来考虑):

  1. 模板的第 j 个元素是字母
  2. 是 '.'
  3. 是 '*'(关键)

时间和空间:O(mn)

代码:

class Solution {
public:
    inline bool is_letter(const char &c) {
        return 'a' <= c && c <= 'z';
    }

    bool isMatch(string s, string p) {
        vector<vector<bool>> dp(32, vector<bool>(32, false));
        dp[0][0] = true;
        int m = s.size(), n = p.size();
        for (int i = 1; i <= m; i++)
            dp[i][0] = false;
        for (int i = 0; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (is_letter(p[j-1])) {
                    if (i > 0 && s[i-1] == p[j-1])
                        dp[i][j] = dp[i-1][j-1];
                }
                else if (p[j-1] == '.') {
                    if (i > 0)
                        dp[i][j] = dp[i-1][j-1];
                }
                else {
                    if (i > 0 && (s[i-1] == p[j-2] || p[j-2] == '.'))
                        dp[i][j] = (dp[i][j-2] || dp[i-1][j]);
                    else
                        dp[i][j] = dp[i][j-2];
                }
            }
        }
        return dp[m][n];
    }
};

22. Generate Parentheses

这个递推式确实比较难想。

怎么说呢,见一题积累一题吧

时间和空间:O(4nn)

代码:

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<vector<string>> dp(n+1);
        dp[0] = { "" };
        dp[1] = { "()" };
        string temp;
        for (int i = 2; i <= n; i++) {
            for (int j = 0; j <= i-1; j++) {
                for (string &x : dp[j]) {
                    for (string &y : dp[i-1-j]) {
                        dp[i].push_back("(" + x + ")" + y);
                    }
                }
            }
        }
        return dp[n];
    }
};

32. Longest Valid Parentheses

一开始以为要用一个二维数组记录 i 到 j 的子串是否是有效括号,但实际上解决不了问题。想复杂了,应该先考虑一维数组能否解决问题。

正确做法:dp数组记录以第 i 个字符结尾的最长的有效括号的长度。

欲求dp[i],首先要看s[i](实际下标可能有微小变化),再看s[i-1],进行分类讨论。关键是以 )) 结尾的情况要思考清楚。

已经见过几种一维dp数组了,第一种是记录前 i 个元素中最大/最长的长度,第二种是记录以第 i 个元素为结尾的最长长度,第三种是记录前 i 个元素的 true 和 false

时间和空间:O(n)

代码:

class Solution {
public:
    int longestValidParentheses(string s) {
        int l = s.size();
        if (l == 0)
            return 0;
        vector<int> dp(l+1, 0);
        dp[0] = dp[1] = 0;
        int ans = 0;
        for (int i = 2; i <= l; i++) {
            if (s[i-1] == '(')
                dp[i] = 0;
            else {
                if (s[i-2] == '(')
                    dp[i] = dp[i-2] + 2;
                else {
                    if (i-dp[i-1]-2 >= 0 && s[i-dp[i-1]-2] == '(')
                        dp[i] = dp[i-dp[i-1]-2] + dp[i-1] + 2;
                    else
                        dp[i] = 0;
                }
            }
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

42. Trapping Rain Water

利用 dp 数组求出每个位置左边最高的柱子高度和右边最高的柱子高度(非常简单),两者的最小值减去该位置的高度即为该位置能接收的雨水量,再扫描一遍整个数组进行求和即可。

时间和空间:O(n)

代码:

class Solution {
public:
    int trap(vector<int>& height) {
        int l = height.size();
        if (l <= 2)
            return 0;
        vector<int> leftmax(l, 0), rightmax(l, 0);
        leftmax[0] = rightmax[l-1] = 0;
        for (int i = 1; i < l; i++) {
            leftmax[i] = max(height[i-1], leftmax[i-1]);
            rightmax[l-i-1] = max(height[l-i], rightmax[l-i]);
        }
        int res = 0, temp = 0;
        for (int k = 1; k < l-1; k++) {
            temp = min(leftmax[k], rightmax[k]) - height[k];
            res += (temp > 0 ? temp : 0);
        }
        return res;
    }
};

53. Maximum Subarray

没什么好说的了,最经典的题目了

这里的dp数组记录的是以i为结尾的数组的最大和,求dp[l-1]即可(可优化空间至 O(1)

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int cur_max = nums[0];
        int ans = cur_max;
        for (int i = 1; i < nums.size(); i++) {
            if (cur_max >= 0)
                cur_max += nums[i];
            else
                cur_max = nums[i];
            ans = max(ans, cur_max);
        }
        return ans;
    }
};

55. Jump Game

dp[i] 表示从第i个下标数字开始能否抵达终点

dp[l-1] = true

从后往前遍历,如果当前数字后面的范围中有一个为true,则dp[i] = true

时间:O(n2)

空间:O(n)

代码:

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int l = nums.size();
        vector<int> dp(l, false);
        dp[l-1] = true;
        for (int i = l-2; i >= 0; i--) {
            for (int j = i+1; j <= min(l-1, i+nums[i]); j++) {
                if (dp[j]) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[0];
    }
};

62. Unique Paths

1) DP

dp[i] [j] 表示到 (i, j) 的道路总数

dp[i][j]=dp[i1][j]+dp[i][j1]

(可优化空间)

时间:O(n2)

空间:O(n)

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<int> dp(n, 1);
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[j] += dp[j-1];
            }
        }
        return dp[n-1];
    }
};

2) 数学方法

组合问题,一下子出结果

时间:O(n)

空间:O(1)

class Solution {
public:
    int uniquePaths(int m, int n) {
        long long ans = 1;
        for (int x = n, y = 1; y < m; ++x, ++y) {
            ans = ans * x / y;
        }
        return ans;
    }
};

64. Minimum Path Sum

dp[i] [j] 表示从(i,j)抵达终点的最小代价,结果即为dp[0] [0]

dp[i][j]=grid[i][j]+min(dp[i+1][j],dp[i][j+1])

  • 可优化空间复杂度
  • 注意边界条件的取值

时间:O(n2)

空间:O(n)

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        vector<int> dp(n, 0);
        int i, j;
        dp[n-1] = grid[m-1][n-1];
        for (j = n-2; j >= 0; j--)
            dp[j] = dp[j+1] + grid[m-1][j];
        for (i = m-2; i >= 0; i--) {
            dp[n-1] += grid[i][n-1];
            for (j = n-2; j >= 0; j--)
                dp[j] = grid[i][j] + min(dp[j+1], dp[j]);
        }
        return dp[0];
    }
};

70. Climbing Stairs

1) DP

斐波那契数列,不解释了

时间:O(n)

空间:O(1)

2) 矩阵快速幂

快速幂可以降低时间复杂度

时间:O(logn)

空间:O(1)

Matrix qpow(Matrix a, int n) {
    Matrix ans(1, 0, 0, 1);
    while (n) {
        if (n & 1)
            ans = ans * a;
        a = a * a;
        n >>= 1;
    }
    return ans;
}

long long climbStairs(int n) {
    Matrix a(1, 1, 1, 0);
    Matrix B = qpow(a, n);
    return B.a21 + B.a22;
}

3) 数学方法

时间和空间:O(1)

72. Edit Distance

dp[i] [j]为字符串1的前i个字符和字符串2的前j个字符之间的编辑距离

递推关系:抓住word1[i-1]和word2[j-1]是否相等来进行分类讨论!

时间和空间:O(mn)

空间可以优化为 O(max{m,n})

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.size(), n = word2.size();
        vector<vector<int> > dp(m+1, vector<int>(n+1, 0));
        for (int i = 0; i <= m; i++)
            dp[i][0] = i;
        for (int j = 0; j <= n; j++)
            dp[0][j] = j;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (word1[i-1] == word2[j-1])
                    dp[i][j] = dp[i-1][j-1];
                else {
                    dp[i][j] = 1 + min(dp[i-1][j], min(dp[i][j-1], dp[i-1][j-1]));
                }
            }
        }
        return dp[m][n];
    }
};

85. Maximal Rectangle

此题从84题进化而来,一定要先弄懂84题的方法!

将每行的高度累加,就能看成很多个84题叠加起来了

DP方法维护每个下标左侧第一个比自己小的高度的下标以及右侧第一个比自己小的高度的下标

利用boundary变量,在从一行进入到下一行的时候根据当前数字是1还是0来动态维护leftlessmin和rightlessmin数组

时间:O(mn)

空间:O(n)

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<int> height(n, 0), leftless(n, -1), rightless(n, n);
        int res = 0, area = 0;
        int boundary = 0;
        for (int i = 0; i < m; i++) {
            // 累加高度
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '0')
                    height[j] = 0;
                else
                    ++height[j];
            }

            // 更新
            boundary = -1;
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == '0') {
                    leftless[j] = -1;
                    boundary = j;
                }
                else {
                    leftless[j] = max(leftless[j], boundary);
                }
            }
            boundary = n;
            for (int j = n-1; j >= 0; j--) {
                if (matrix[i][j] == '0') {
                    rightless[j] = n;
                    boundary = j;
                }
                else {
                    rightless[j] = min(rightless[j], boundary);
                }
            }

            // 计算每个下标处的最大面积
            for (int j = 0; j < n; j++) {
                area = height[j] * (rightless[j]-leftless[j]-1);
                res = max(res, area);
            }
        }
        return res;
    }
};

96. Unique Binary Search Trees

dp[i]代表从1到i这i个节点能构成多少棵二叉树

实际上只要把每个节点都当成根节点一次再相加就能够递推了

时间:O(n2)

空间:O(n)

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

121. Best Time to Buy and Sell Stock

dp[i] 表示前 i 天所能得到的最大收益

则根据第 i 天的情况分类讨论

  • 空间复杂度可以优化

时间:O(n)

空间:O(1)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int minprice = prices[0], maxprofit = 0;
        for (int i = 1; i < prices.size(); i++) {
            if (prices[i] > minprice)
                maxprofit = max(maxprofit, prices[i]-minprice);
            else
                minprice = prices[i];
        }
        return maxprofit;
    }
};

122(*). Best Time to Buy and Sell Stock II

两状态DP

dp0[i] 表示第 i 天不持有股票的情况下,前 i 天的最大收益

dp1[i] 表示第 i 天持有股票的情况下,前 i 天的最大收益

最后返回 dp0[n-1] 即可

时间和空间:O(n)

空间复杂度可以优化为 O(1)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int yesmax = -prices[0], nomax = 0;
        int ty = 0, tn = 0;
        for (int i = 1; i < prices.size(); i++) {
            ty = yesmax;
            tn = nomax;
            yesmax = max(ty, tn-prices[i]);
            nomax = max(tn, ty+prices[i]);
        }
        return nomax;
    }
};

123(*). Best Time to Buy and Sell Stock III

188(*). Best Time to Buy and Sell Stock IV

dp[i] [j] 表示前 j 天完成了 (i+1) 次操作(包含买卖)的情况下的最大收益

空间可以优化为 O(1)

答案显然为 dp[1] [l-1],dp[3] [l-1]... 的最大值

139. Word Break

dp[i]表示字符串s[0...i]能否被分割

则dp[i]可以由前(i-1)个状态来推断出来

时间:O(n2)

空间:O(n)

  • 剪枝
    1. 统计出词典中最长和最短的词的长度
    2. 只记录能被分割的下标

剪枝代码:

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> str_set;
        int minlength = 0, maxlength = 0;
        int l = 0;
        for (const auto str: wordDict) {
            if (str_set.count(str))
                continue;
            str_set.insert(str);
            l = str.size();
            minlength = min(minlength, l);
            maxlength = max(maxlength, l);
        }
        
        int ls = s.size();
        vector<int> idx_arr;
        idx_arr.push_back(-1);
        for (int i = 0; i < ls; i++) {
            for (int j = 0; j < idx_arr.size(); j++) {
                if (i-idx_arr[j] > maxlength && j == idx_arr.size()-1)
                    return false;
                if (i-idx_arr[j] < minlength || i-idx_arr[j] > maxlength)
                    continue;
                if (str_set.count(s.substr(idx_arr[j]+1, i-idx_arr[j]))) {
                    idx_arr.push_back(i);
                    break;
                }
            }
        }
        
        if (idx_arr[idx_arr.size()-1] == ls-1)
            return true;
        else
            return false;
    }
};

一般的dp数组代码:

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        if (s.size() == 0)
            return true;
        if (wordDict.size() == 0)
            return false;
        int minlen, maxlen;
        minlen = maxlen = wordDict[0].size();
        unordered_set<string> pool;
        for (const auto str: wordDict) {
            minlen = min(minlen, (int)str.size());
            maxlen = max(maxlen, (int)str.size());
            pool.insert(str);
        }
        int l = s.size();
        if (l < minlen)
            return false;
        vector<bool> dp(l+1, false);  // dp[i]表示s[0...i-1]是否能被拆分
        dp[0] = true;
        int i, j;
        int begin;
        for (i = 1; i <= l; i++) {
            if (i < minlen) {
                dp[i] = false;
                continue;
            }
            begin = max(0, i-maxlen);
            for (j = i-minlen; j >= begin; j--) {
                if (pool.count(s.substr(j, i-j)) && dp[j]) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[l];
    }
};

152. Maximum Product Subarray

多状态dp(此处为2状态,最大和最小)

类似于53题最大连续子序列和

由于有负数出现,这题要维护2个状态,一个是最大状态,一个是最小状态

dp[i]表示以i结尾的最大/最小子数组乘积

时间和空间:O(n)

空间可以优化为 O(1)

未优化代码:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int l = nums.size();
        vector<int> maxdp(l, 0), mindp(l, 0);
        maxdp[0] = mindp[0] = nums[0];
        int ans = maxdp[0];
        for (int i = 1; i < l; i++) {
            if (nums[i] == 0)
                maxdp[i] = mindp[i] = 0;
            else if (nums[i] > 0) {
                maxdp[i] = max(nums[i], maxdp[i-1] * nums[i]);
                mindp[i] = min(nums[i], mindp[i-1] * nums[i]);
            }
            else {
                maxdp[i] = max(nums[i], mindp[i-1] * nums[i]);
                mindp[i] = min(nums[i], maxdp[i-1] * nums[i]);
            }
            ans = max(ans, maxdp[i]);
        }
        return ans;
    }
};

官方代码:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int maxvalue = nums[0], minvalue = nums[0];
        int maxres = maxvalue;
        int mx, mn;
        for (int i = 1; i < nums.size(); i++) {
            mx = maxvalue;
            mn = minvalue;
            maxvalue = max(mx * nums[i], max(mn * nums[i], nums[i]));
            minvalue = min(mx * nums[i], min(mn * nums[i], nums[i]));
            maxres = max(maxres, maxvalue);
        }
        return maxres;
    }
};

188(*). Best Time to Buy and Sell Stock IV

见123题

198. House Robber

不解释了,经典题之一

dp[i]:前 i 个……最大

时间和空间:O(n)

空间可以优化为 O(1)

class Solution {
public:
    int rob(vector<int>& nums) {
        int l = nums.size();
        if (l == 1)
            return nums[0];
        vector<int> maxvalue(l);
        maxvalue[0] = nums[0];
        maxvalue[1] = max(nums[0], nums[1]);
        for (int i = 2; i < l; i++)
            maxvalue[i] = max(maxvalue[i - 1], maxvalue[i - 2] + nums[i]);
        return maxvalue[l - 1];
    }
};

221. Maximal Square

思路比较新奇

dp[i] [j] 表示以 (i, j) 为右下角的最大矩形的边长

if matrix[i] [j] == '1':

dp[i][j]=1+min{dp[i1][j],dp[i][j1],dp[i1][j1]}

else:

dp[i][j]=0

时间和空间:O(mn)

空间可优化为 O(n)

未优化代码:

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int> > dp(m+1, vector<int>(n+1, 0));
        int res = 0;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (matrix[i-1][j-1] == '1') {
                    dp[i][j] = 1 + min(dp[i-1][j-1], min(dp[i][j-1], dp[i-1][j]));
                    res = max(res, dp[i][j]);
                }
            }
        }
        return res * res;
    }
};

优化代码:

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int> > dp(2, vector<int>(n+1, 0));
        int res = 0;
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (matrix[i-1][j-1] == '1') {
                    dp[i%2][j] = 1 + min(dp[(i+1)%2][j-1], min(dp[i%2][j-1], dp[(i+1)%2][j]));
                    res = max(res, dp[i%2][j]);
                }
                else
                    dp[i%2][j] = 0;
            }
        }
        return res * res;
    }
};

279. Perfect Squares

dp[i] 表示数字 i 最少表示为x个数字的平方和

正向传递即可

时间:O(nn)

空间:O(n)

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n+1);  // dp[0] = 0
        for (int i = 1; i <= n; i++) {
            dp[i] = 1 + dp[i-1];
            for (int j = 2; j*j <= i; j++) {
                dp[i] = min(dp[i], 1+dp[i-j*j]);
            }
        }
        return dp[n];
    }
};

300. Longest Increasing Subsequence

dp[i] 表示以 i 结尾的最长上升子序列的长度

要得到 dp[i] ,只需要从0到i-1逐个比较即可

时间:O(n2)

空间:O(n)

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

309. Best Time to Buy and Sell Stock with Cooldown

三状态同时 dp

多状态dp要注意同时更新的问题,即要先将所有状态储存在临时变量中,再用临时变量对所有状态进行更新

用一个二维数组 dp[3] [l] 来进行维护

dp[0] [i] 表示第 i 天持有股票的情况下,前 i 天的最大收益

dp[1] [i] 表示第 i 天不持有股票且不是冷冻期的情况下,前 i 天的最大收益

dp[2] [i] 表示第 i 天不持有股票且是冷冻期的情况下,前 i 天的最大收益

时间和空间:O(n)

空间复杂度可以优化为 O(1)

未优化:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int l = prices.size();
        vector<vector<int> > dp(3, vector<int>(l, 0));
        dp[0][0] = -prices[0];
        dp[1][0] = dp[2][0] = 0;
        for (int j = 1; j < l; j++) {
            dp[0][j] = max(dp[0][j-1], dp[1][j-1]-prices[j]);
            dp[1][j] = max(dp[1][j-1], dp[2][j-1]);
            dp[2][j] = dp[0][j-1] + prices[j];
        }
        return max(dp[1][l-1], dp[2][l-1]);
    }
};

优化:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int l = prices.size();
        vector<int> dp(3);
        dp[0] = -prices[0];
        dp[1] = dp[2] = 0;
        vector<int> t(3, 0);
        for (int i = 1; i < l; i++) {
            for (int k = 0; k < 3; k++)
                t[k] = dp[k];
            dp[0] = max(t[0], t[1]-prices[i]);
            dp[1] = max(t[1], t[2]);
            dp[2] = t[0] + prices[i];
        }
        return max(dp[1], dp[2]);
    }
};

312. Burst Balloons

dp[i] [j] 表示在开区间 (i, j) 中填满数字所能得到的最大值

递推式:

需要反向思考:区间中最后一个被戳破的气球是哪个?

时间:O(n2×n)=O(n3)

空间:O(n2)

自底向上进行dp:

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        int l = nums.size();
        vector<int> new_nums(l+2, 1);
        for (int i = 1; i <= l; i++)
            new_nums[i] = nums[i-1];
        vector<vector<int> > dp(l+2, vector<int>(l+2, 0));
        int i = 0, j = 0, k = 0;
        int adder = 0;
        for (int u = 2; u < l+2; u++) {
            for (i = 0, j = i+u; j < l+2; i++, j++) {
                for (k = i+1; k <= j-1; k++) {
                    adder = new_nums[i] * new_nums[k] * new_nums[j];
                    dp[i][j] = max(dp[i][j], adder + dp[i][k] + dp[k][j]);
                }
            }
        }
        return dp[0][l+1];
    }
};

自顶向下进行记忆化搜索:

class Solution {
public:
    int solve(vector<vector<int> > &dp, vector<int> &a, int left, int right) {
        if (left >= right-1)
            return 0;
        if (dp[left][right] != -1)
            return dp[left][right];
        int adder = 0;
        for (int k = left+1; k <= right-1; k++) {
            adder = a[left] * a[k] * a[right];
            dp[left][right] = max(dp[left][right], solve(dp, a, left, k)+adder+solve(dp, a, k, right));
        }
        return dp[left][right];
    }

    int maxCoins(vector<int>& nums) {
        int l = nums.size();
        vector<vector<int> > dp(l+2, vector<int>(l+2, -1));
        vector<int> new_nums(l+2, 1);
        for (int i = 0; i < l; i++)
            new_nums[i+1] = nums[i];
        return solve(dp, new_nums, 0, l+1);
    }
};

322. Coin Change

完全背包问题的变式,要求背包被恰好填满,且所用物品的总数最少

dp[i] [j] 表示放入前 i 件物品的情况下,填满容量为 j 的背包所需要的最少硬币

dp[m-1] [amount] 即为所求结果

时间和空间:O(mamount)

空间可优化O(amount)

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

337. House Robber III

对某棵子树维护两个值,一个是选取了根节点后的最大值,另一个是不选取根节点的最大值

最后的结果是根节点的两个值取最大

时间和空间:O(n)

空间可以被优化为 O(1)

哈希表:

class Solution {
public:
    void robhelp(unordered_map<TreeNode *, int> &picked, unordered_map<TreeNode *, int> &unpicked, TreeNode* subroot) {
        if (subroot == nullptr)
            return;
        robhelp(picked, unpicked, subroot->left);
        robhelp(picked, unpicked, subroot->right);
        picked[subroot] = subroot->val + unpicked[subroot->left] + unpicked[subroot->right];
        unpicked[subroot] = max(picked[subroot->left], unpicked[subroot->left]) + max(picked[subroot->right], unpicked[subroot->right]);
    }

    int rob(TreeNode* root) {
        unordered_map<TreeNode *, int> picked, unpicked;
        picked[nullptr] = unpicked[nullptr] = 0;
        robhelp(picked, unpicked, root);
        return max(picked[root], unpicked[root]);
    }
};

使用struct进行空间优化(由于没有采用红黑树实现的unordered_map,所以时间也有所优化):

class Solution {
public:
    struct Result {
        int choosen, unchoosen;
    };

    Result robhelp(TreeNode *subroot) {
        if (!subroot)
            return {0, 0};
        Result lbranch = robhelp(subroot->left);
        Result rbranch = robhelp(subroot->right);
        Result ans;
        ans.choosen = subroot->val + lbranch.unchoosen + rbranch.unchoosen;
        ans.unchoosen = max(lbranch.choosen, lbranch.unchoosen) + max(rbranch.choosen, rbranch.unchoosen);
        return ans;
    }

    int rob(TreeNode* root) {
        Result res = robhelp(root);
        return max(res.choosen, res.unchoosen);
    }
};

338. Counting Bits

第 i 个数字包含的 1 相当于最后一位的1的个数加上其右移1位得到的数字的1的个数之和

时间和空间:O(n)

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> dp(n+1, 0);
        dp[0] = 0;
        for (int i = 1; i <= n; i++)
            dp[i] = (i&1) + dp[i>>1];
        return dp;
    }
};

416. Partition Equal Subset Sum

0-1 背包问题的变式

dp[i] [j] 表示前i个物体(取或不取)能否恰好填满容量为 j 的背包

时间和空间:O(lhalf)

空间可优化O(half)

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

494. Target Sum

法1:

dp[i] [j] 表示使用了前 i 个物品后“容量” j 的可能性总量(以1000为对称中心,即1000代表0)

时间和空间:O(ltotal)

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int l = nums.size();
        vector<vector<int> > dp(l+1, vector<int>(2001, 0));
        dp[0][1000] = 1;
        for (int i = 1; i <= l; i++) {
            for (int j = 0; j <= 2000; j++) {
                if (dp[i-1][j] > 0) {
                    dp[i][j-nums[i-1]] += dp[i-1][j];
                    dp[i][j+nums[i-1]] += dp[i-1][j];
                }
            }
        }
        return dp[l][1000+target];
    }
};

法2:

变为0-1背包问题的变式了

时间和空间:O(lneg)

空间可优化O(neg)

class Solution {
public:
   int findTargetSumWays(vector<int>& nums, int target) {
       int l = nums.size();
       int sum = 0;
       for (int i = 0; i < l; i++) {
           sum += nums[i];
       }
       if (sum - target < 0 || (sum-target)%2 == 1)
           return 0;
       int neg = (sum-target) / 2;
       vector<int> dp(neg+1, 0);
       dp[0] = 1;
       for (int i = 0; i < l; i++) {
           for (int j = neg; j >= nums[i]; j--) {
               dp[j] += dp[j-nums[i]];
           }
       }
       return dp[neg];
   }
};

647. Palindromic Substrings

类似于第5题,这里不讲了

posted @   PanSTARRS  阅读(102)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示