动态规划 - 归档

LeetCode - 动态规划

概述

方式性质
递归自顶向下,对于有重叠子问题的递归,可以使用记忆化搜索。
动态规划有重叠子问题和最优子结构的递归,可以转换成自底向上的动态规划。
具体解以LeetCode 300为例寻找所有的具体解。0-1背包问题具体解,图表上面反向推一下。

背包问题

完全背包问题:每个物品可以无限使用。
多重背包问题:每个物品不止一个,有nums[i]个。
多维费用背包问题:要考虑物品体积和重量两个维度(三维数组)
更多约束:物品之间加入约束,排斥或者依赖。

01背包-要求恰好取到背包容量

416 Partition Equal Subset Sum

01背包-求方案数

494 Target Sum

完全背包-要求恰好取到背包容量

322 Coin Change

完全背包-求方案数

518 Coin Change II

二维费用背包

474 Ones and Zeroes

序列问题

300 Longest Increasing Subsequence
1143 Longest Common Subsequence

树形DP

120 Triangle

其他

139 Word Break
377 Combination Sum IV

注意顺序性和DP维度

32 Longest Valid Parentheses
5 Longest Palindromic Substring
10 Regular Expression Matching
72 Edit Distance
62 Unique Paths
63 Unique Paths II
53 Maximum Subarray
64 Minimum Path Sum
96 Unique Binary Search Trees
121 Best Time to Buy and Sell Stock
70 Climbing Stairs
152 Maximum Product Subarray
221 Maximal Square
309 Best Time to Buy and Sell Stock with CoolDown
338 Counting Bits
85 Maximal Rectangle
312 Burst Balloons
343 Integer Break
279 Perfect Squares
91 Decode Ways
213 House Robber II
337 House Robber III
376 Wiggle Subsequent

9-9 LCS最长公共子序列
dijkstra单源最短路径也是动态规划
shortestPath(x) = min (shortestPath(a) + w(a->x))

程序员代码面试指南

换钱的最少货币数(完全背包)

动态规划:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

#define MAXVALUE 0x7fffffff
using namespace std;
int main() {
	int n;
	int aim;
	cin >> n;
	cin >> aim;
	vector<int> arr(n, 0);
	for (auto& x : arr) {
		cin >> x;
	}
	vector<vector<unsigned int>> dp(n, vector<unsigned int>(aim + 1, MAXVALUE));
	for (int c = 0; c < aim + 1; c++) {
		dp[0][c] = ((c % arr[0]) ? MAXVALUE : c / arr[0]);
	}
	for (int i = 1; i < n; i++) {
		for (int c = 0; c < aim + 1; c++) {
			dp[i][c] = dp[i - 1][c];
			if (c - arr[i] >= 0) {
				dp[i][c] = min(dp[i][c], dp[i][c - arr[i]] + 1);
			}
		}
	}
	if (dp[n - 1][aim] < MAXVALUE)
		cout << dp[n - 1][aim];
	else//cout << dp[n - 1][aim] << -1 将-1按照无符号打印
		cout << -1;
	return 0;
}

递归形式,时间没有通过:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

using namespace std;
//memo[i][c] 代表[0, i]区间内的货币面值,凑齐c的最少张数
//memo[i][c] = memo[i-1][c - k*arr[i]] + k;

int minCoins(vector<int>& arr, vector<vector<int>>& memo, int i, int aim){
    if(memo[i][aim] != -1)
        return memo[i][aim];
    else if(i == 0){
        if(!aim%arr[0]){
            memo[i][aim] = aim/arr[0];
            return memo[i][aim];
        }
        else
            return -1;
    }
    else if(aim == 0){
        memo[i][aim] = 0;
        return memo[i][aim];
    }
    int res = -1;
    for(int k = 0; k <= aim/arr[i]; k++){
        int next = minCoins(arr, memo, i-1, aim-k*arr[i]);
        if(next != -1){
            res = ((res == -1) ? next + k : min(res, next + k));
        }
    }
    memo[i][aim] = res;
    return memo[i][aim];
}
int main() {
	int n;
	int aim;
	cin >> n;
	cin >> aim;
	vector<int> arr(n, 0);
    vector<vector<int>> memo(n, vector<int>(aim + 1, -1));
	for (auto& x : arr) {
		cin >> x;
	}
    cout << minCoins(arr, memo, n - 1, aim);
	return 0;
}

矩阵最小路径和

递归形式:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

#define MAXVALUE 0x7fffffff
using namespace std;

int pathSum(vector<vector<int>> &arr, vector<vector<int>> &memo, int& n, int& m, int i, int j){
    if(memo[i][j] != MAXVALUE)
        return memo[i][j];
    
    if(i == n - 1 && j == m - 1){
        memo[i][j] = arr[i][j];
        return memo[i][j];
    }
    else if (i == n - 1){
        memo[i][j] = arr[i][j] + pathSum(arr, memo, n, m, i, j + 1); 
        return memo[i][j];
    }
    else if (j == m - 1){
        memo[i][j] = arr[i][j] + pathSum(arr, memo, n, m, i + 1, j);
        return memo[i][j];
    }
    else{
        memo[i][j] = arr[i][j] + min(pathSum(arr, memo, n, m, i + 1, j), pathSum(arr, memo, n, m, i, j + 1));
        return memo[i][j];
    }
}
int main() {
	int n;
	int m;
	cin >> n;
	cin >> m;
	vector<vector<int>> arr(n, vector<int>(m, 0));
    vector<vector<int>> memo(n, vector<int>(m, MAXVALUE));
	for (auto& x : arr) {
        for (auto& y : x) {
		    cin >> y;
	    }
    }
    cout << pathSum(arr, memo, n, m, 0, 0);

	return 0;
}

动态规划:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

#define MAXVALUE 0x7fffffff
using namespace std;

int main() {
	int n;
	int m;
	cin >> n;
	cin >> m;
	vector<vector<int>> arr(n, vector<int>(m, 0));
    vector<vector<int>> dp(n, vector<int>(m, 0));
	for (auto& x : arr) {
        for (auto& y : x) {
		    cin >> y;
	    }
    }
    //dp[i][j] = min(dp[i-1][j] + arr[i][j], dp[i][j-1] + arr[i][j])
    //初始化第一行、第一列的dp为起始位置
    dp[0][0] = arr[0][0];
    for(int i = 1; i < m; i++){
        dp[0][i] = dp[0][i-1] + arr[0][i];
    }
     for(int j = 1; j < n; j++){
        dp[j][0] = dp[j-1][0] + arr[j][0];
    }
    for(int i = 1; i < n; i++){
        for(int j = 1; j < m; j++){
            dp[i][j] = min(dp[i][j-1], dp[i-1][j]) + arr[i][j];
        }
    }
    cout << dp[n-1][m-1];
    
	return 0;
}

空间压缩:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

#define MAXVALUE 0x7fffffff
using namespace std;

int main() {
	int n;
	int m;
	cin >> n;
	cin >> m;
	vector<vector<int>> arr(n, vector<int>(m, 0));
    vector<int> dp(m, 0);
	for (auto& x : arr) {
        for (auto& y : x) {
		    cin >> y;
	    }
    }
    //dp[i][j] = min(dp[i-1][j] + arr[i][j], dp[i][j-1] + arr[i][j])
    //初始化第一行、第一列的dp为起始位置
    dp[0] = arr[0][0];
    for(int i = 1; i < m; i++){
        dp[i] = dp[i-1] + arr[0][i];
    }
    for(int i = 1; i < n; i++){
        for(int j = 0; j < m; j++){
            if(j == 0)
                dp[j] += arr[i][j];
            else
                dp[j] = min(dp[j-1], dp[j]) + arr[i][j];
        }
    }
    cout << dp[m-1];
    
	return 0;
}

LeetCode

32. Longest Valid Parentheses

1. 暴力枚举

class Solution {
public:
    int longestValidParentheses(string s) {
        int maxlength = 0;
        for(int i = 0; i < s.size(); i++){
            for(int j = i + 1; j < s.size(); j++){
                if(s[i] == '(' && s[j] == ')' && !((j - i + 1) % 2)){
                    if(isValid(string(s, i, j - i + 1)))
                    maxlength = max(maxlength, j - i + 1);
                }
            }
        }
        return maxlength;
    }
    bool isValid(string s) {
		stack<char> st;
		for (auto c : s) {
			if (st.empty()) {
				st.push(c);
			}
			else {
				switch (c) {
				case ')':
					if (st.top() == '(')
						st.pop();
					else st.push(c);
					break;
				case ']':
					if (st.top() == '[')
						st.pop();
					else st.push(c);
					break;
				case '}':
					if (st.top() == '{')
						st.pop();
					else st.push(c);
					break;
				default:
					st.push(c);
					break;
				}
			}
		}
		if (st.empty())
			return true;
		else
			return false;
	}
};

2. 栈的用法

class Solution {
public:
    int longestValidParentheses(string s) {
        int maxlength = 0;
        stack<int> st;
        st.push(-1);

        for(int i = 0; i < s.size(); i++){
            if(s[i] == '('){
                st.push(i);
            }
            else{
                st.pop();
                if(!st.empty()){
                    maxlength = max(maxlength, i - st.top());
                }
                else{
                    st.push(i);
                }
            }
        }
        return maxlength;
    }
};

3. 动态规划

动态规划,这道题的状态表达式和状态转移方程不太好找。
dp[i] 是必须以s[i]为结束字符’)'的最长有效括号的长度。
dp[i] = dp[i-2] + 2
= dp[i-1] + 2 + dp[i - dp[i-1] - 2];


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

5. Longest Palindromic Substring

1.暴力枚举

for i 0->size
for j i->size
isPalindrome

class Solution {
public:
    string longestPalindrome(string s) {
        if(s.length() <= 1)
        return s;
        int maxLength = 0;
        int L;
        for(int i = 0; i < s.size(); i++){
            for(int j = i; j < s.size(); j++){
                if(isPalindrome(s, i, j)){
                    if(maxLength < j - i + 1){
                        maxLength = j - i + 1;
                        L = i;
                    }
                }
            }
        }
        return string(s, L, maxLength);
    }
private:
    bool isPalindrome(string &s, int start, int end){
        while(start <= end){
            if(s[start] == s[end]){
                start++;
                end--;
            }
            else{
                return false;
            }
        }
        return true;
    }
};

2.中心扩展

for i 1->size
Length_centerExpand

class Solution {
public:
    string longestPalindrome(string s) {
        if(s.length() <= 1)
        return s;
        string temp;
        string ret;
        int maxR = 0;
        int maxC = 0;
        temp.push_back('#');
        for(auto& c : s){
            temp.push_back(c);
            temp.push_back('#');
        }
        for(int i = 1; i < temp.size(); i++){
            int iR = max(maxR, Length_centerExpand(temp, i));
            if(maxR < iR){
                maxR = iR;
                maxC = i;
            }
        }
        for(int i = maxC - maxR; i <= maxC + maxR; i++){
            if(temp[i] != '#'){
                ret.push_back(temp[i]);
            }
        }
        return ret;
    }
private:
    int Length_centerExpand(string &s, int iCenter){
        int ilength = 0;
        int iLeft = iCenter;
        int iRight = iCenter;
        while(--iLeft >= 0 && ++iRight <= s.size()){
            if(s[iLeft] == s[iRight]){
                ilength++;
            }
            else{
                break;
            }
        }
        return ilength;
    }
};

3.暴力递归(长度)

class Solution {
public:
	//F(i,j) [i, j]区间字符串是否是回文
	//F(i,j) == (s[i] == s[j] && F(i+1,j-1))
	//       == (s[i] != s[j] && F(i,j-1))
	//       == (s[i] != s[j] && F(i-1,j))
	int longestPalindrome(string s) {
		if (s.length() <= 1)
			return s.length();

		return palindLength_R(s, 0, s.size() - 1);

	}
private:
	int palindLength_R(string s, int l, int r) {
		if (l > r)
			return 0;
		else if (l == r)
			return 1;
		if (s[l] == s[r]) {
			return palindLength_R(s, l + 1, r - 1) + 2;
		}
		else {
			return max(palindLength_R(s, l + 1, r), palindLength_R(s, l, r - 1));

		}
	}
};

4.暴力递归优化(长度)

class Solution {
public:
	//F(i,j) [i, j]区间字符串是否是回文
	//F(i,j) == (s[i] == s[j] && F(i+1,j-1))
	//       == (s[i] != s[j] && F(i,j-1))
	//       == (s[i] != s[j] && F(i-1,j))
	int longestPalindrome(string s) {
		if (s.length() <= 1)
			return s.length();

		vector<vector<int>> memo(s.size(), vector<int>(s.size(), 1));

		return palindLength_R(s, 0, s.size() - 1, memo);

	}
private:
	int palindLength_R(string s, int l, int r, vector<vector<int>>& memo) {
		if (l > r)
			return 0;
		else if (l == r) {
			memo[l][r] = 1;
			return 1;
		}
		if (s[l] == s[r]) {
			memo[l][r] = palindLength_R(s, l + 1, r - 1, memo) + 2;
			return memo[l][r];
		}
		else {
			memo[l][r] = max(palindLength_R(s, l + 1, r, memo), palindLength_R(s, l, r - 1, memo));
			return memo[l][r];
		}
	}
};

5.动态规划(长度?)

class Solution {
public:
	//F(i,j) [i, j]区间最大回文子串长度(i <= j)
	//F(i,j) == (s[i] == s[j] ? F(i+1,j-1) + 2 : max(F(i,j-1), F(i-1,j))
	//dp(i,j) == (s[i] == s[j] ? dp(i+1,j-1) + 2 : max(dp(i,j-1), dp(i-1,j))
	int longestPalindrome(string s) {
		if (s.length() <= 1)
			return s.length();

		vector<vector<int>> dp(s.size(), vector<int>(s.size(), 1));
		for (unsigned int j = 1; j < s.size(); j++) {
			for (unsigned int i = 0, k = j; k < s.size(); k++, i++) {
				dp[i][k] = (s[i] == s[k] ? dp[i + 1][k - 1] + 2 : max(dp[i][k - 1], dp[i + 1][k]));
				printf("s[%d] == %c, s[%d] == %c, dp[%d][%d] == %d\n", i, s[i], k, s[k], i, k, dp[i][k]);
			}
		}
		return dp[0][s.size() - 1];
	}
};

5.动态规划(求子串,遍历方式1)

class Solution {
public:
	//dp(i,j), [i, j]是否构成回文字符串
	//dp(i,j) = (s[i] == s[j] && dp(i+1,j-1))
    //[i, j]区间需要执行不同区间长度的dp操作
    //注意和回文长度状态表达式dp(i,j) = s[i] == s[j] ? dp(i+1,j-1) + 2区别开来
	string longestPalindrome(string s) {
		if (s.length() <= 1)
			return s;
        int maxLength = 1;
        int start = 0;
		vector<int> dp(s.size(), 0);
        //初始化长度为1和2时候的值
        for(int i = 0; i < s.size(); i++){
            dp[i] = 1;
            if(i + 1 < s.size() && s[i] == s[i+1]){
                dp[i+1] = 1;
                start = i;
                maxLength = 2;
            }
        }
        //求解其他长度是否为回文字符串
		for (int len = 2; len < s.size(); len++) {
			for (int i = 0; i < s.size() - len; i++) {
				dp[i][i+len] = (s[i] == s[i+len] && dp[i+1][i+len-1]);
                //printf("dp[%d][%d] == %d\n", i, j, dp[i][j]);
                if(dp[i][i+len] && maxLength < (len + 1)){
                    maxLength = len + 1;
                    start = i;
                }
			}
		}
		return s.substr(start, maxLength);
	}
}; 

5.动态规划(求子串,遍历方式2)

class Solution {
public:
	//dp(i,j), [i, j]是否构成回文字符串
	//dp(i,j) = (s[i] == s[j] && dp(i+1,j-1))
    //[i, j]区间需要执行不同区间长度的dp操作
    //注意和回文长度状态表达式dp(i,j) = s[i] == s[j] ? dp(i+1,j-1) + 2区别开来
	string longestPalindrome(string s) {
		if (s.length() <= 1)
			return s;
        int maxLength = 1;
        int start = 0;
        vector<vector<int>> dp(s.size(), vector<int>(s.size(), 0));
        //遍历方式为:j -> [0, s.size()-1]
        //            i -> [0, j]
        //初始化:初始化[0,0]
        for(int j = 0; j < s.size(); j++){
            for(int i = 0; i <= j; i++){
                if(i == j || (j - i == 1 && s[i] == s[j])){
                    dp[i][j] = 1;
                }
                else{
                    dp[i][j] = (s[i] == s[j] && dp[i+1][j-1]);
                }
                if(dp[i][j] && (maxLength < j - i + 1)){
                    maxLength = j - i + 1;
                    start = i;
                }
            }
	    }
        return s.substr(start, maxLength);
    }
}; 

10. Regular Expression Matching

1. 动态规划

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size();
        int n = p.size();
        vector<vector<bool>> dp(m+1, vector<bool>(n+1, false));
        dp[0][0] = true;
        for(int j = 1; j <= n; j++){
            if(p[j-1] == '*'){
                dp[0][j] = dp[0][j - 2];
            }
            cout << j << " "<< dp[0][j] << endl;
        }
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(p[j-1] == '.' || p[j-1] == s[i-1]){
                    dp[i][j] = dp[i-1][j-1];
                }
                else if(p[j-1] == '*' && j - 2 >= 0){
                    //if(j - 3 >= 0 && s[i-1] == p[j - 3]){ 
                        dp[i][j] = dp[i][j] || dp[i][j-2];
                    //}
                    if(s[i-1] == p[j - 2] || p[j - 2] == '.'){
                        dp[i][j] = dp[i][j] || dp[i-1][j];
                    }
                }
                cout << i << " " << j << " "<< dp[i][j] << endl;
            }
        }
        return dp[m][n];
    }
};

72. Edit Distance

1. 动态规划

二维的状态表达式,一个维度描述一个字符串的状态。

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

62. Unique Paths

#DP# #C++#

1. 动态规划

状态表达式和转移方程很好写,子问题模型区别于回文字符串。

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

53. Maximum Subarray

1. 动态规划

dp[i]表示[0,i]范围并且必须以i为结束的范围内,能取得的最大值
dp[i] = (dp[i-1] > 0 ? dp[i-1] + nums[i] : nums[i])

class Solution {
public:
    //状态表达式怎么写?
    //dp[i][j]表示数组中[i,j]范围取得的最大值?
    //dp[i][j] = dp[i][j-1]???
    //dp[i]表示数组[0,i]范围取得的最大值?
    //dp[i] = dp[i-1] + (s[i] > 0 ? s[i] : 0)????
    //dp[i]表示[0,i]范围并且必须以i为结束的范围内,能取得的最大值。
    //dp[i] = (dp[i-1] > 0 ? dp[i-1] + nums[i] : nums[i]);

    //O(n)类型的dp有点不太好找状态表达式,感觉有点生搬硬套。
    //与O(n2)的dp不同,这道题最优解不是dp[nums.size()-1],需要记录每次dp的值。
    int maxSubArray(vector<int>& nums) {
        int dp[nums.size()] = {0};
        int maxSum = nums[0];
        dp[0] = nums[0];
        for(int i = 1; i < nums.size(); i++){
            dp[i] = (dp[i-1] > 0 ? dp[i-1] + nums[i] : nums[i]);
            maxSum = max(maxSum, dp[i]);
            cout << i << " " << dp[i] << endl;
        }
        return maxSum;
    }
};

63. Unique Paths II

1. 动态规划

class Solution {
public:
    //dp[i][j]: 到达(i,j)的路径数
    //dp[i][j] = dp[i][j-1] + dp[i-1][j], dp需要处理障碍物所在坐标的情况
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int row = obstacleGrid.size();
        int col = obstacleGrid[0].size();
        vector<vector<unsigned int>> dp(row, vector<unsigned int>(col, 0));
        for(int j = 0; j < col; j++){
            if(obstacleGrid[0][j] != 1)
               dp[0][j] = 1;
            else
                break;
        }
        for(int i = 1; i < row; i++){
            for(int j = 0; j < col; j++){
                dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : j - 1 < 0 ? dp[i-1][j] : dp[i][j-1] + dp[i-1][j];
            }
        }
        return dp[row-1][col-1];
    }
};

2. 记忆化递归

class Solution {
public:
    //helper(0,0) ->helper(2,2)
    //helper(i,j) = helper(i-1, j) + helper(i, j-1), helper需要处理障碍物所在坐标的情况。
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        vector<vector<int>> memo(obstacleGrid.size(), vector<int>(obstacleGrid[0].size(), -1));
        return helper(obstacleGrid, obstacleGrid.size()-1, obstacleGrid[0].size()-1, memo);
    }
private:
    int helper(vector<vector<int>>& obstacleGrid, int i, int j, vector<vector<int>>& memo){
        if(obstacleGrid[i][j] == 1)
            return 0;
        if(i < 0 || j < 0)
            return 0;
        if(memo[i][j] != -1)
            return memo[i][j];

        else if(i == 0 && j != 0)
            return memo[i][j] = helper(obstacleGrid, i, j-1, memo);
        else if(j == 0 && i != 0)
            return memo[i][j] = helper(obstacleGrid, i-1, j, memo);
        else if(j != 0 && i != 0)
            return memo[i][j] = helper(obstacleGrid, i-1, j, memo) + helper(obstacleGrid, i, j-1, memo);
        else
            return 1;
        
    }
};

64. Minimum Path Sum

1. 动态规划

评论说有用DFS/BFS求解,接下来用这种方式试试看(虽然超时,但是巩固下思想)。

class Solution {
public:
    //dp[i][j], 表示的是到达[i,j]这个点的最小路径和
    //dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
    int minPathSum(vector<vector<int>>& grid) {
        int n = grid[0].size();
        int m = grid.size();
        int dp[m][n] = {0};
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(i == 0 && j == 0){
                    dp[0][0] = grid[0][0];
                }
                else{
                    dp[i][j] = min(i >= 1 ? dp[i-1][j] : dp[i][j-1], j >= 1 ? dp[i][j-1] : dp[i-1][j]) + grid[i][j];
                }
            }
        }
        return dp[m-1][n-1];
    }
};

96. Unique Binary Search Trees

1. 动态规划

这是目前见到的唯一一个状态表达式是一维表达式,但状态的求解却非常数时间的dp.

class Solution {
public:
    //dp[n]表示取到n的时候,一共有多少BST。
    //F[i,n]表示取到n的时候,以第i个数字为根节点的BST,有多少种表示方法。
    //dp[n] = F[1,n] + F[2,n] + ... + F[n,n]
    //F[i,n] = dp[i-1] * dp[n-i]
    //dp[n]  = dp[0]*dp[n-1] + dp[1]*dp[n-2] + ... + dp[n-1][0]
    //以上dp[i]的每个状态求解时间都不是常数,而是依赖i值大小来的循环求解(代码内侧循环)。
    int numTrees(int n) {
        int dp[n+1] = {0};
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            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

1. 动态规划

又是一维动态规划体型,感觉不太好确立状态表达式:
dp[i]: 前面i天能够获取的最大利润
dp[i] = max(dp[i-1],prices[i] - min)

class Solution {
public:
    //dp[i]: 在第i天卖出能获取到的最大利润???
    //dp[i] = dp[i-1] + prices[i] - prices[i-1]

    //dp[i][j]: 在i天买入,j天卖出获取到的利润?
    //dp[i][j] = ?????????????

    //dp[i]: 前面i天能够获取的最大利润
    //dp[i] = max(dp[i-1],prices[i] - min) 
    int maxProfit(vector<int>& prices) {
        if(prices.empty()){
            return 0;
        }
        int minPrice = prices[0];
        int dp[prices.size()] = {0};
        dp[0] = 0;
        for(int i = 1; i < prices.size(); i++){
            minPrice = min(minPrice, prices[i]);
            dp[i] = max(dp[i-1],prices[i] - minPrice);
        }
        return dp[prices.size()-1];
    }
};

参考:
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/gu-piao-wen-ti-python3-c-by-z1m/

139. Word Break

1. 递归

1.递归+记忆化搜索
2.递归其实就是穷举,但是这道题不太好想出来穷举的方式。

参考与链接(侵删):https://zxi.mytechroad.com/blog/leetcode/leetcode-139-word-break/
image.png

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> inDict(wordDict.begin(), wordDict.end());
        return wordBreaks(s, inDict);
    }
private:
    bool wordBreaks(string const& s, unordered_set<string>& inDict){
        if(mem.count(s)){
            return mem[s];
        }
        bool ret = false;
        if(s.empty())
            return true;
        for(int i = 0; i < s.size(); i++){
            ret |=  (wordBreaks(s.substr(0,i), inDict) && inDict.count(s.substr(i, s.size()-i)));
        }
        mem[s] = ret;
        return mem[s];
    }
    unordered_map<string, bool> mem;
};

2. 动态规划

1.动态规划
2.状态表达式和转移方程比上面递归好想一些
3.求解一个状态表达式时间不是常量,与状态表达式的规模成正比。

class Solution {
public:
    //dp[i]: 表示s的前i个字符组成的子串是否可以拆解分成字典中的单词
    //dp[i] = dp[i-1] && dict(s.substr(i-1,1)) || dp[i-2] && dict(s.substr(i-2,2)) || dp[i-3] && dict(s.substr(i-3,3))
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> inDict(wordDict.begin(), wordDict.end());
        bool dp[s.size() + 1] = {0};
        dp[0] = true;
        for(int i = 1; i <= s.size(); i++){
            for(int j = i - 1; j >= 0; j--){
                //dp[i] |=  dp[j] && inDict.count(s.substr(j, i - j));
                //不用遍历完所有子问题,有个满足就可以退出了
                if(dp[j] && inDict.count(s.substr(j, i - j))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.size()];
    }
};

3.宽度有限搜索?

70. Climbing Stairs

1. 递归

递归+记忆化搜索

class Solution {
public:
    //dp[i]: 前i个梯子有多少种方法
    //dp[i] = dp[i-1] + dp[i-2]
    //用递归的话,将dp[i]转化为递归函数climbStair()
    //转化为递归函数climbStair(i) = climbStair(i-1) + climbStair(i-2)
    int climbStairs(int n) {
        if(memo.count(n)){
            return memo[n];
        }
        if(n == 0 || n == 1){
            return 1;
        }
        else{
            memo[n] = climbStairs(n-1) + climbStairs(n-2);
            return memo[n];
        }
    }

private:
    map<int, int> memo;
};

2. 动态规划

class Solution {
public:
    //dp[i]: 前i个梯子有多少种方法
    //dp[i] = dp[i-1] + dp[i-2]
    int climbStairs(int n) {
        int dp[n+1] = {0};
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
    }
};

152. Maximum Product Subarray

1. 动态规划

DP
注意状态转移方程是否经得起推敲
a5306c52dbd3e1c17abb18d5cece0a7.jpg

class Solution {
    //考虑穷举情况下,时间复杂度O(N2),那么动态规划应该会优化到O(N)
    //dp[i] 前i项最大的乘积???
    //dp[i] = (dp[i-1] * nums[i-1] > 0) ? dp[i-1] * nums[i-1] : dp[i-1] ???(错误的状态转移方程)
    //dp[i] 以i结尾的子序列的最大/小的乘积
    //dmax[i] = max(dmax[i-1]*nums[i-1], nums[i-1]);
    //dmin[i] = min(dmin[i-1]*nums[i-1], nums[i-1]);
public:
    int maxProduct(vector<int>& nums) {
        int dmax[nums.size() + 1] = {0};
        int dmin[nums.size() + 1] = {0};
        dmax[0] = 1;
        dmin[0] = 1;
        int ret = 0x80000000;
        for(int i = 1; i <= nums.size(); i++){
            if(nums[i - 1] < 0){
                int temp = dmax[i-1];
                dmax[i-1] = dmin[i-1];
                dmin[i-1] = temp;
            }
            dmax[i] = max(dmax[i-1]*nums[i-1], nums[i-1]);
            dmin[i] = min(dmin[i-1]*nums[i-1], nums[i-1]);
            ret = max(ret, dmax[i]);
        }              
        return ret;
    }
};

221. Maximal Square

1. 动态规划

注意状态转移方程
注意dp数组是dp[m + 1][n + 1],扩充1方便初始化dp

class Solution {
public:
    //dp[i][j]: 以[i,j]为右下角的最大的正方形的边长,是范围内最优解
    //dp[i][j] = min(dp[i-1][j], dp[i-1][j-1], dp[i][j-1]) + 1
    int maximalSquare(vector<vector<char>>& matrix) {
        if(matrix.empty())
            return 0;
        int m = matrix.size();
        int n = matrix[0].size();
        int dp[m + 1][n + 1] = {};
        memset(dp, 0, sizeof(dp));
        int maxLength = 0;
//       for(int j = 0; j < matrix[0].size(); j++){
//          dp[0][j] = (matrix[0][j] == '1' ? 1 : 0); 
//           maxLength = max(maxLength, dp[0][j]);
//        }
//       for(int i = 0; i < matrix.size(); i++){
//            dp[i][0] = (matrix[i][0] == '1' ? 1 : 0); 
//            maxLength = max(maxLength, dp[i][0]);
//        }
        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(matrix[i-1][j-1] == '0'){
                    dp[i][j] = 0;
                }
                else{
                    dp[i][j] = min(min(dp[i-1][j], dp[i-1][j-1]), dp[i][j-1]) + 1;
                }
                maxLength = max(maxLength, dp[i][j]);
                //cout << maxLength << endl;
            }
        }
        return maxLength*maxLength;
    }
};

279. Perfect Squares

1. 动态规划

  1. DP 有点类似于背包问题中的完全背包问题

  2. 需要明确的是n值越大,最优解趋向于利用j中更大的平方数来划分出子问题
    image.png

  3. 应该需要优化一下时间效率
    image.png

class Solution {
public:
    //完全背包问题?
    //dp[i]和为i的时候,需要的最少的完全平方数的个数
    //dp[i] = dp[i-j*j] + 1(j是子问题削减掉的完全平方数)
    int numSquares(int n) {
    int dp[n + 1] = {0};
    //初始化, 用最差的情况来初始化,比如n==12, 那么最差的情况是12个1相加
    for(int i = 0; i <=n ; i++){
        dp[i] = i;
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j*j <= i; j++){
            dp[i] = min(dp[i], dp[i-j*j] + 1);
        }
        cout << dp[i] <<endl;
    }
    return dp[n];
    }
};

2. 宽度优先搜索

BFS

91. Decode Ways

1. 动态规划

根据最后两个字符的不同情况,有三个状态转移方程,比较难确定。

class Solution {
public:
    //dp[i]: 从左到第i个字符不同编码的总数
    int numDecodings(string s) {
        if(s[0] == '0')
            return 0;
        vector<int> dp(s.size() + 1, 0);
        dp[0] = 1; dp[1] = 1;
        for(int i = 2; i <= s.size(); i++){
            if(s[i-1] == '0'){
                dp[i] = s[i-2] == '0' || s[i-2] > '2' ? 0 : dp[i-2];
            }
            else{
                if(s[i-2] == '1' || (s[i-2] == '2' && s[i-1] < '7'))
                    dp[i] = dp[i-1] + dp[i-2];
                else
                    dp[i] = dp[i-1];
            }
        }
        return dp[s.size()];
    }
};

213. House Robber II

环形DP

1. 动态规划

class Solution {
public:
    //dp[i]: 偷取到i家时共计能偷取多少钱
    //dp[i] = max(dp[i-2] + nums[i], dp[i-1]);
    int rob(vector<int>& nums) {
        if(nums.empty())
            return 0;
        if(nums.size() == 1)
            return nums[0];
        vector<int> dp1(nums.size()+1, 0);
        vector<int> dp2(nums.size()+1, 0);
        dp1[0] = 0;
        dp1[1] = nums[0];
        dp2[0] = 0;
        dp2[1] = 0;
        for(int i = 2; i < nums.size(); i++){
            dp1[i] = max(i-2 < 0 ? nums[i-1] : dp1[i-2] + nums[i-1], dp1[i-1]);
        }
        for(int i = 2; i <= nums.size(); i++){
            dp2[i] = max(i-2 < 0 ? nums[i-1] : dp2[i-2] + nums[i-1], dp2[i-1]);
        }
        return max(dp1[nums.size()-1], dp2[nums.size()]);
    }
};

2. 记忆化递归

class Solution {
public:
    //helper(i): 偷取到i家时共计能偷取多少钱
    //helper(i) = max(helper(i-2) + nums[i], helper(i-1))
    int rob(vector<int>& nums) {
        if(nums.size() == 1)
            return nums[0];
        vector<int> memo1(nums.size()+1, -1);
        vector<int> memo2(nums.size()+1, -1);
        return max(helper(true, nums, nums.size()-2, memo1), helper(false, nums, nums.size()-1, memo2));
    }
private:
    int helper(bool robFist, vector<int>& nums, int i, vector<int>& memo){
        if(i == 0 && robFist == true)
            return nums[0];
        else if(i == 0 && robFist == false)
            return 0;
        else if(i < 0)
            return 0;
        if(memo[i] != -1)
            return memo[i];

        return memo[i] = max(helper(robFist, nums, i-2, memo) + nums[i], helper(robFist, nums, i-1, memo));
    }
};

337. House Robber III

树形DP.
在这里插入图片描述

1.记忆化递归

自顶向下代码结构中,递归调用点比较多,并且需要运用哈希表来存储子节点计算结果。

class Solution {
public:
    int rob(TreeNode* root) {
        if(root == NULL)
            return 0;
            unordered_map<TreeNode*, int> memo;
        //return max(helper(root, memo), helper(root->left, memo) + helper(root->right, memo));
        //<<<<<<<<<<<<<<
            return helper(root, memo);
        //>>>>>>>>>>>>>

    }
private:
    int helper(TreeNode* node, unordered_map<TreeNode*, int>& memo){
        if(node == NULL)
            return 0;
        if(node->left == NULL && node->right == NULL)
            return node->val;
        if(memo.count(node))
            return memo[node];
        int ret = node->val;
        if(node->left)
            ret += helper(node->left->left, memo) + helper(node->left->right, memo);
        if(node->right)
            ret += helper(node->right->left, memo) + helper(node->right->right, memo);
        return memo[node] = max(ret, helper(node->left, memo) + helper(node->right, memo));
    }
};

2. 动态规划

自底向上。

  1. 参数是树节点的指针,并且在题目条件下,无法转换为数组用树形DP来处理,所以只有使用递归。
  2. 使用后序遍历方法来做,代码依然是递归结构,但是后续遍历属于自底向上的处理流程,将两个左右子节点作为子问题来返回到当前节点来求最优解,这与DP的思想一致。
  3. 子问题返回ret[2], ret[0]是不考虑子节点值,ret[1]是考虑子节点值。
class Solution {
public:
    int rob(TreeNode* root) {
        if(root == NULL)
            return 0;
        vector<int> ret(2, 0);
        ret = helper(root);
        return max(ret[0], ret[1]);
    }
private:
    vector<int> helper(TreeNode* node){
        vector<int> ret(2, 0);
        vector<int> left(2, 0);
        vector<int> right(2, 0);
        if(node == NULL)
            return ret;
        left = helper(node->left);
        right = helper(node->right);
        ret[0] = max(left[0], left[1]) + max(right[0], right[1]);
        ret[1] = node->val + left[0] + right[0];
        return ret;
    }
};

376. Wiggle Subsequent

1.记忆化递归

在这里插入图片描述

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.empty())
            return 0;
        bool Up = true;
        int ret = 0;
        vector<vector<int>> memo(nums.size(), vector<int>(2, -1));
        return 1 + max(helper(nums, 0, Up, memo), helper(nums, 0, !Up, memo));
    }
private:
    int helper(vector<int>& nums, int iStart, bool isUp, vector<vector<int>>& memo){
        if(memo[iStart][isUp] != -1)
            return memo[iStart][isUp];
            int retLength = 0;
        for(int i = iStart + 1; i < nums.size(); i++){
            if((isUp && nums[iStart] < nums[i]) || (!isUp && nums[iStart] > nums[i])){
                    retLength = max(retLength, helper(nums, i, !isUp, memo) + 1);
                }
        }
        return memo[iStart][isUp] = retLength;
    }
};

2. 动态规划

和记忆化递归有着相同的递推表达式,但是遍历方向相反,DP从右往左规划的话对比起来比较直观。

class Solution {
public:
    //dp[i][0]: 以i结尾并且属于波谷时候的最长wiggle sequence.
    //dp[i][1]: 以i结尾并且属于波谷时候的最长wiggle sequence.
    //dp[i][0] = if nums[j] > nums[i]: max of dp[j][1] + 1, for j -> [0, i)
    //dp[i][1] = if nums[j] < nums[i]: max of dp[j][1] + 1, for j -> [0, i)
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.empty())
            return 0;
        vector<vector<int>> dp(nums.size(), vector<int>(2, 1));
        for(int j = 0; j < 2; j++){
            dp[0][j] = 1;
        }
        for(int i = 1; i < nums.size(); i++){
            for(int j = 0; j < i; j++){
                if(nums[j] > nums[i]){
                    dp[i][0] = max(dp[i][0], dp[j][1] + 1);
                }
                if(nums[j] < nums[i]){
                    dp[i][1] = max(dp[i][1], dp[j][0] + 1);
                }
            }
        }
        return max(dp[nums.size()-1][1], dp[nums.size()-1][0]);
    }
};

3. 调试过程中发现的方式

调试的时候发现这个也能通过,是在官方的第一个解上面做了点改动,但是第二点的逻辑我自己也不太明白。 执行用时:4 ms 内存消耗:7.7 MB
1.加入了备忘录memo(这个不是重点)
2.在条件判断里面直接return,并且加入了else分支:return memo[iStart][isUp] = helper(nums, i, isUp, memo)
发现这样做能够将时间复杂度提升很多, 如果是直接在官方版本上面加入备忘录的话不会超时但是时间效率很差32ms。

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.empty())
            return 0;
        bool Up = true;
        int ret = 0;
        vector<vector<int>> memo(nums.size(), vector<int>(2, -1));
        return 1 + max(helper(nums, 0, Up, memo), helper(nums, 0, !Up, memo));
    }
private:
    int helper(vector<int>& nums, int iStart, bool isUp, vector<vector<int>>& memo){
        if(memo[iStart][isUp] != -1)
            return memo[iStart][isUp];
        for(int i = iStart + 1; i < nums.size(); i++){
            if((isUp && nums[iStart] < nums[i]) || (!isUp && nums[iStart] > nums[i])){
                    return memo[iStart][isUp] = helper(nums, i, !isUp, memo) + 1;
                }
            else{
                    return memo[iStart][isUp] = helper(nums, i, isUp, memo);
                } 
            }
        return 0;
    }
};

300. Longest Increasing Subsequence

暴力解法:O((2^n)*n)

1. 动态规划

DP
解题思路和279.perfect squares 是一样的.
image.png

class Solution {
public:
    //dp[i], 以索引i结尾的最长上升子序列的长度,必须能取到i.
    //求解每个状态都不是常量时间,与自己规模i成正比.
    //dp[i] = max(dp[i], dp[j] + 1), j -> [0, i).
    //时间复杂度O(n2)与一些二维状态表达式的题一样.
    int lengthOfLIS(vector<int>& nums) {
        int maxLength = 1;
        if(nums.size() == 0)
            return 0;
        //初始化每个索引上面的长度为1
        vector<int> dp(nums.size(), 1);
        for(int i = 0; i < nums.size(); i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    dp[i] = max(dp[i], dp[j] + 1);
                }
               maxLength = max(maxLength, dp[i]); 
            }
        }
        return maxLength;
    }
};

2. 递归

memo数组全部初始化为1,这就是基本状态。
O(n^2)

3. 二分查找?

O(nlogn)解法,不属于动态规划。

1143 Longest Common Subsequence

Input: text1 = “abcde”, text2 = “ace”
Output: 3
Explanation: The longest common subsequence is “ace” and its length is 3.

1. 动态规划

class Solution {
public:
    //dp[i][j]: 串1的[0,i]与串2的[0,j]的最长公共子序列长度
    //dp[i][j] = str1[i] == str2[j] ? dp[i-1][j-1] + 1 : max(dp[i-1]dp[j], dp[i][j-1])
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size() + 1, vector<int>(text2.size() + 1, 0));
        for(int i = 1; i <= text1.size(); i++){
            for(int j = 1; j <= text2.size(); j++){
                dp[i][j] = (text1[i-1] == text2[j-1] ? dp[i-1][j-1] + 1 : max(dp[i-1][j], dp[i][j-1]));
            }
        }
         return dp[text1.size()][text2.size()];
    }
};

2. 递归

class Solution {
public:
    //memo[i][j]: 串1的[0,i]与串2的[0,j]的最长公共子序列长度
    //memo[i][j] = str1[i] == str2[j] ? helper(i-1, j-1) + 1 : max(helper(i-1, j), helper(i, j-1))
    //加上记忆化递归
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> memo(text1.size(), vector<int>(text2.size(), -1));
        return helper(text1.size() - 1, text2.size() - 1, text1, text2, memo);
    }
    int helper(int r1, int r2, string & text1, string & text2, vector<vector<int>>& memo){
        if(r1 < 0 || r2 < 0)
            return 0;
        if(memo[r1][r2] != -1)
            return memo[r1][r2];
        if(text1[r1] == text2[r2])
            return memo[r1][r2] = helper(r1-1, r2-1, text1, text2, memo) + 1;
        else
            return memo[r1][r2] = max(helper(r1, r2-1, text1, text2, memo), helper(r1-1, r2, text1, text2, memo));
    }
};

120. 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).

1. 无返回值递归(超时)

class Solution {
public:
    //习惯于用无返回值的递归结构来求解(超时)
    int minimumTotal(vector<vector<int>>& triangle) {
        minSum = INT_MAX;
        helper(triangle, 0, 0, 0);
        return minSum;
    }
private:
    void helper(vector<vector<int>>& triangle, int row, int col, int sum){
        if(row == triangle.size()){
            minSum = min(sum, minSum);
            return;
        }
        sum += triangle[row][col];
        for(int i = row; i < triangle[row].size(); i++){
            helper(triangle, row+1, col, sum);
            helper(triangle, row+1, col+1, sum);
        }
        return;
    }
    int minSum;
};

2. 带返回值递归记忆化搜索

class Solution {
public:
    //递归
    //helper(i, j) = min(helper(i-1, j), helper(i-1, j-1)) + trianle[i][j];
    int minimumTotal(vector<vector<int>>& triangle) {
        int minSum = INT_MAX;
        vector<vector<int>> memo(triangle.size(), vector<int>(triangle.size(), -1));
        for(int j = 0; j < triangle.size(); j++){
        minSum = min(minSum, helper(triangle, triangle.size()-1, j, memo));
        }
        return minSum;
    }
private:
    int helper(vector<vector<int>>& triangle, int row, int col, vector<vector<int>>& memo){
        if(row == 0){
            return triangle[0][0];
        }
        if(memo[row][col] != -1)
            return memo[row][col];
        memo[row][col] = min(col >= triangle[row-1].size() ? INT_MAX : helper(triangle, row-1, col, memo), 
                   col == 0 ? INT_MAX : helper(triangle, row-1, col-1, memo)) + triangle[row][col];
        return memo[row][col];
    }
};

3. 动态规划

																									class Solution {
public:
    //dp[i][j]: i行j列元素为叶子节点的路线当中形成的最小值
    //dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + trianle[i][j];
    int minimumTotal(vector<vector<int>>& triangle) {
        //vector<vector<int>> dp(triangle.size(), triangle[triangle.size()-1].size());
        vector<vector<int>> dp = triangle;
        dp[0][0] = triangle[0][0];
        for(int i = 1; i < triangle.size(); i++){
            for(int j = 0; j < triangle[i].size(); j++){
                dp[i][j] = min(j == 0 ? INT_MAX : dp[i-1][j-1], j >= dp[i-1].size() ? INT_MAX : dp[i-1][j]) + triangle[i][j];
            }
        }
        int minSum = INT_MAX;
        for(auto& x : dp[triangle.size()-1]){
            minSum = min(minSum, x);
        }
        return minSum;
    }
};

309. Best Time to Buy and Sell Stock with Cooldown

1. 动态规划

DP

6e6fd51487bd5285241a64dd8cc07ef.jpg

class Solution {
public:
    //dp[i]: 在第i天执行买入卖出或者暂停交易所获取的最大利润
    //buy[i] = max(buy[i-1], cool[i-1] - prices[i])
    //sell[i] = buy[i-1] + prices[i]
    //cool[i] = max(cool[i-1], sell[i-1])
    int maxProfit(vector<int>& prices) {
        //int maxProfit = 0;

        //int buy[prices.size()] = {0};
        //int sell[prices.size()] = {0};
        //int cool[prices.size()] = {0};
        if(prices.empty())
            return 0; 
        //初始化第一天
        int buy = -prices[0];//第一天买入利润为支出
        int sell = INT_MIN;//第一天不可能卖出,初始化为最小值
        int cool = 0;//第一天也可以不用交易,利润为0
        for(int i = 1; i < prices.size(); i++){
            int prev_sell = sell;
            sell = buy + prices[i];
            buy = max(buy, cool - prices[i]);
            cool = max(cool, prev_sell);
        }
        return max(max(buy, sell), cool);
    }
};

338. Counting Bits

1. 移位操作统计

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> ret;
        for(int i = 0; i <= num; i++){
            int u = i;
            int count = 0;
            for( ; u != 0; u >>= 1){
                count += u & 1;
            }
            ret.push_back(count);
        }
        return ret;
    }
};

2. 动态规划

DP, bit位上面找规律

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> ret;
        vector<int> dp(num + 1, 0);
        dp[0] = 0;
        ret.push_back(0);
        for(int i = 1; i <= num; i++){
            dp[i] = dp[i/2] + i%2;
            ret.push_back(dp[i]);
        }
        return ret;
    }
};

494. Target Sum

1. 递归枚举

DFS

class Solution {
public:
    //#Brute Force# #DFS#
    int findTargetSumWays(vector<int>& nums, int S) {
        ways = 0;
        findTargetSumWay(nums, 0, 0, S, true);
        findTargetSumWay(nums, 0, 0, S, false);
        return ways;
    }

private:
    void findTargetSumWay(vector<int>& nums, int depth, int sum, int& S, bool opt){
        if(opt == true)
            sum += nums[depth];
        else
            sum -= nums[depth];
        if(depth == nums.size()-1){
            if(sum == S){
                ways++;
            }
            return;
        }
        findTargetSumWay(nums, depth+1, sum, S, true);
        findTargetSumWay(nums, depth+1, sum, S, false);
    }
    int ways;
};

2. 动态规划

DP
image.png

class Solution {
public: 
    //#DP# #可以转化为背包问题?#
    //dp[i][j]: 前i个数组成和j的方法数
    //dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]]
    //需要处理j-nums[i]为负值的情况,需要处理[j+/-nums[i]]越界问题
    //初始化dp[0][offset]的时候需要注意第一个数nums[0]是否为0
    int findTargetSumWays(vector<int>& nums, int S) {
        int size = nums.size();
        int sum = 0;
        for(int v : nums){
            sum += v;
        }
        if(size == 1 && sum != abs(S) || sum < abs(S)){
            return 0;
        }
        int offset = sum;
        vector<vector<int>> dp(nums.size(),vector<int>(2*sum+1, 0));
        if(nums[0] == 0) dp[0][offset] = 2;
        else{
        dp[0][offset + nums[0]] = 1;
        dp[0][offset - nums[0]] = 1;
        }
        for(int i = 1; i < nums.size(); i++){
            for(int j = 0; j <= 2*sum; j++){
                dp[i][j] = (j-nums[i] >= 0 ? dp[i-1][j-nums[i]] : 0) + (j + nums[i] <= 2*sum ? dp[i-1][j + nums[i]] : 0);
            }
        }
        return dp[size-1][offset+S];
    }
};

附一个别人的代码,但是递推公式没有看明白(和官方题解相同):
https://zxi.mytechroad.com/blog/dynamic-programming/leetcode-494-target-sum/

85. Maximal Rectangle

#单调栈#

1. 动态规划

主要应用84题来计算当前行可以得到的最大的矩形,说是DP比较勉强。

class Solution {
public:
//dp[i][j]: [i,j]元素为底的这列'1'有多高
//dp[i][j] = dp[i][j-1] + 1;
    int maximalRectangle(vector<vector<char>>& matrix) {
        if(matrix.empty()){
            return 0;
        }
        int maxFilled = 0;
        vector<vector<int>> dp(matrix.size(), vector<int>(matrix[0].size(), 0));
        for(int j = 0; j < dp.size(); j++){
            //cout << j << " ";
            for(int i = 0; i < dp[0].size(); i++){
                //dp[j][i] = matrix[j][i] == '1' ? 1 + dp[j-1][i] : dp[j-1][i];
                dp[j][i] = matrix[j][i] == '1' ? (j - 1 >= 0 ? dp[j-1][i] + 1 : 1) : 0;
                //cout << i << " ";
            }

            //cout << endl;
            maxFilled = max(maxFilled, largestRectangleArea(dp[j]));
        }
        return maxFilled;
    }
private:
    int largestRectangleArea(vector<int>& heights) {
        stack<int> myStack;
        int maxRect = 0;
        int height = 0;
        myStack.push(0);
        heights.push_back(0);
        for (int i = 1; i < heights.size(); i++) {
            while (!myStack.empty() && heights[i] < heights[myStack.top()]) {
                height = heights[myStack.top()];
                myStack.pop();
                maxRect = max(maxRect, height * (myStack.empty() ? i : i - myStack.top() - 1)); 
            }
            myStack.push(i);
        }
        heights.pop_back();
        return maxRect;
    }
};

312. Burst Balloons

1. 暴力回溯

回溯
e2042f39fb03171ea6321420b3f9644.jpg

class Solution {
public:
//backTrack:提取第i个数所获得的coins.
    int maxCoins(vector<int>& nums) {
        int coin = 0;
        maxCoin = 0;
        backTrack(nums, coin);
        return maxCoin;
    }
private:
    void backTrack(vector<int>& nums, int& coin){
        if(nums.empty()){
            maxCoin = max(maxCoin, coin);
            return;
}
        for(int i = 0; i < nums.size(); i++){
            int tempI = nums[i];
            int tempCoin = nums[i] * (i - 1 < 0 ? 1 : nums[i-1]) * (i + 1 >= nums.size() ? 1 : nums[i+1]);
            coin += tempCoin;
            nums.erase(nums.begin() + i);
            backTrack(nums, coin);
            nums.insert(nums.begin() + i, tempI);
            coin -= tempCoin;
        }
        return;
    }
    int maxCoin;
};

2. 递归

递归+记忆化
捕获.PNG

class Solution {
public:
    //递归+记忆化
    //memo[i][j]: 打爆范围为[i,j]的气球所获得的最大coins.
    //memo[i][j] = nums[i-1]*nums[k]*nums[j+1] + memo[i][k-1] + memo[k+1][j];
    int maxCoins(vector<int>& nums) {
        nums.insert(nums.begin(), 1);
        nums.push_back(1);
        vector<vector<int>> memo(nums.size(), vector<int>(nums.size(), 0));
        maxCoin(nums, 1, nums.size() - 2, memo);
        return memo[1][nums.size() - 2];
    }
private:
    int maxCoin(vector<int>& nums, int l, int r, vector<vector<int>>& memo) {
        if(memo[l][r] != 0){
            return memo[l][r];
        }
        if(l > r){
            return 0;
        }
        if(l == r){
            memo[l][r] = nums[l-1]*nums[l]*nums[l+1];
            return memo[l][r];
        }
        for(int k = l; k <= r; k++){
            int add = nums[k] * nums[l-1] * nums[r+1];
            memo[l][r] = max(memo[l][r], maxCoin(nums, l, k-1, memo) + maxCoin(nums, k+1, r, memo) + add);
        }
        return memo[l][r];
    }
};

3. 动态规划

DP

class Solution {
public:
    //动态规划
    //dp[i][j]: 打爆范围为[i,j]的气球所获得的最大coins.
    //dp[i][j] = nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j];
    int maxCoins(vector<int>& nums) {
        nums.insert(nums.begin(), 1);
        nums.push_back(1);
        vector<vector<int>> dp(nums.size(), vector<int>(nums.size(), 0));
        for(int l = 1; l <= nums.size() - 2; l++){
            for(int i = 1; i < nums.size() - l; i++){
                int j = i + l - 1;
                for(int k = i; k <= j; k++){
                    int add = nums[k] * nums[i-1] * nums[j+1];
                    dp[i][j] = max(dp[i][j], dp[i][k-1] + dp[k+1][j] + add);
                }
            }
        }
        return dp[1][nums.size() - 2];
    }
};

416. Partition Equal Subset Sum

1. 动态规划

0-1背包问题变形
[1,5,11,5] -> [1,5,5] + [11]
转化为完全填满背包问题,即遍历数组是否可以刚好填满sum/2,返回的是bool类型
[0, i]内的数据能否刚好填充c大小的背包:
F(i, c) = F(i-1, c) || F(i-1, c-w(i)), 时间复杂度O(n*sum)
F(i, 0) = true
5d9e6f1b538d5ac789fddb4267c0938.jpg

class Solution {
public:
    //0-1背包问题变形
    //dp[i][j]: 前i个数能否组成和为j的组合
    //dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
    //dp[0][0-C]为初始化行(dp[0][0] = true, C = sum/2)
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(auto& x : nums){
            sum += x;
        }
        if(sum%2)
            return false;
        int C = sum / 2;
        vector<vector<bool>> dp(nums.size(), vector<bool>(C+1, false));
        dp[0][0] = true;
        for(int j = 0; j <= C; j++){
            dp[0][j] = (j == nums[0]);
        }
        for(int i = 1; i < nums.size(); i++){
            for(int j = 0; j <= C; j++){
                dp[i][j] = dp[i-1][j] || (j-nums[i] >= 0 ? dp[i-1][j-nums[i]] : false);
            }
        }
        return dp[nums.size()-1][C];
    }
};

2. 空间优化

for i <- 1 to N
for j <- C to 0

class Solution {
public:
    //0-1背包问题变形
    //dp[j]: 能否组成和为j的组合, bool类型
    //dp[j] = dp[j] || dp[j-nums[i]]
    //先给dp初始化(dp[0] = true, C = sum/2)
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(auto& x : nums){
            sum += x;
        }
        if(sum%2)
            return false;
        int C = sum / 2;
        //增加的第一列代表C=0.
        vector<bool> dp(C+1, false);
        //空元素可以组成和为C=0
        dp[0] = true;
        for(int j = 1; j <= C; j++){
            dp[j] = (j == nums[0]);
        }
        for(int i = 1; i < nums.size(); i++){
            for(int j = C; j >= 0; j--){
                dp[j] = dp[j] || (j-nums[i] >= 0 ? dp[j-nums[i]] : false);
            }
        }
        return dp[C];
    }
};

2. 递归枚举

应该递归枚举还是回溯?

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(auto& x : nums){
            sum += x;
        }
        if(sum%2)
            return false;
        int C = sum / 2;
    return helper(0, 0, nums, C);
    }

private:
    bool helper(int i, int sum, vector<int>& nums, int& C){
        if(i == nums.size() - 1){
            return sum + nums[i] == C || sum == C;
        }
        return helper(i+1, sum, nums, C) || helper(i+1, sum + nums[i], nums, C);
    }
};

3. 记忆化递归

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0;
        for(auto& x : nums){
            sum += x;
        }
        if(sum%2)
            return false;

        memo.clear();
        for(int i = 0 ; i < nums.size() ; i ++)
            memo.push_back(vector<int>(sum / 2 + 1, -1));

        return tryPartition(nums, nums.size() - 1, sum/2);
    }
private:
    bool tryPartition(vector<int>& nums, int index, int sum){
        if(sum == 0){
            return 1;
        }
        else if(index < 0 || sum < 0)
            return 0;
        if(memo[index][sum] != -1)
            return memo[index][sum];
        
        memo[index][sum] = tryPartition(nums, index - 1, sum) || tryPartition(nums, index - 1, sum - nums[index]);
        return memo[index][sum];
    }
    vector<vector<int>> memo;
};

322. Coin Change

1. 动态规划

被背包问题搞混了思路:
dp[i][j]: 前i种硬币凑满金额j用到的最少的硬币数量???
dp[i][j] = min of -> dp[i-1][j-k*coins[i]] + k???

状态转移表达式
基础表达式dp[i][j] = min of k -> dp[i-1][j-k*coins[i]] + k
优化到O(MN)dp[i][j] = min of (dp[i][j-coins[i]] + 1, dp[i-1][j])
空间压缩dp[i] = min for j -> dp[i-coins[j]] + 1
class Solution {
public:


    //dp[i][j] = min(dp[i][j-coins[i]] + 1, dp[i-1][j-coins[i]] + 1)
    int coinChange(vector<int>& coins, int amount) {
        vector<vector<int>> dp(coins.size(), vector<int>(amount + 1, INT_MAX / 2));
        //初始化dp第一排
        for(int j = 0; j <= amount; j++){
            dp[0][j] = j % coins[0] ? INT_MAX / 2 : j / coins[0];
        }
        for(int i = 1; i < coins.size(); i++){
            for(int j = 0; j <= amount; j++){
                dp[i][j] = j-coins[i] < 0 ? dp[i-1][j] : min(dp[i][j-coins[i]] + 1, dp[i-1][j]);
            }
        }
        return dp[coins.size() - 1][amount] == INT_MAX / 2 ? -1 : dp[coins.size() - 1][amount];
    }
};

2. 空间压缩

class Solution {
public:
    //被背包问题搞混了思路:
    //dp[i][j]: 前i种硬币凑满金额j用到的最少的硬币数量???
    //dp[i][j] = min of -> dp[i-1][j-k*coins[i]] + k???
     
    //一维状态表达式:
    //dp[i]: 组成金额i所需要的最少的硬币数量
    //dp[i] = min for dp[i-coins[j]] + 1 , j =[0,n-1]
    
    //如何从dp[i-1][j-k*coins[i]] + k 到 dp[i-coins[j]] + 1???
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX/2);
        dp[0] = 0;
        for(int i = 1; i <= amount; i++){
            for(int j = 0; j < coins.size(); j++){
                dp[i] = min(dp[i], i-coins[j] >= 0 ? dp[i-coins[j]] + 1 : dp[i]);
            }
        }
        return dp[amount] == INT_MAX/2 ? -1 : dp[amount];
    }
};

518. Coin Change II

状态转移表达式
基础表达式dp[i][j] = dp[i-1][j] + max of dp[i-1][j-k*coins[i]]
优化到O(MN)dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]]
空间压缩dp[j] = dp[j] + dp[j-coins[i]]

1. 动态规划

dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]]
nums = {1,2,5} amount = 5.

012345
1111111
2112233
5100001
class Solution {
public:
    //dp[i][j]: 前i个元素能够组成aount的组合数量
    //dp[i][j] = dp[i-1][j] + dp[i][j-coins[i]]
    int change(int amount, vector<int>& coins) {
        if(coins.empty())
            if(amount == 0)
                return 1;
            else
                return 0;
        vector<vector<int>> dp(coins.size(), vector<int>(amount+1, 0));
        for(int j = 0; j <= amount; j++){
            dp[0][j] = j % coins[0] ? 0 : 1;
        }
        for(int i = 1; i < coins.size(); i++){
            for(int j = 0; j <= amount; j++){
                //转移时只考虑之前元素的组合情况 + 转移时算上当前元素i的组合情况
                dp[i][j] = dp[i-1][j] + (j-coins[i] < 0 ? 0 : dp[i][j-coins[i]]);
            }
        }
        return dp[coins.size()-1][amount];
    }
};

2. 空间压缩

class Solution {
public:
	//
    int change(int amount, vector<int>& coins) {
        if(coins.empty())
            if(amount == 0)
                return 1;
            else
                return 0;
        vector<int> dp(amount+1, 0);
        for(int j = 0; j <= amount; j++){
            dp[j] = j % coins[0] ? 0 : 1;
        }
        for(int i = 1; i < coins.size(); i++){
            for(int j = 0; j <= amount; j++){
                //转移时只考虑之前元素的组合情况 + 转移时算上当前元素i的组合情况
                dp[j] = dp[j] + (j-coins[i] < 0 ? 0 : dp[j-coins[i]]);
            }
        }
        return dp[amount];
    }
};

474. Ones and Zeroes

1. 动态规划

class Solution {
public:
    //0-1背包的二维费用问题
    //dp[k][i][j]: 考虑strs里[0,k-1]范围的字符串,i个0和j个1最多能够组多少个字符串
    //dp[k][i][j] = max of dp[k-1][i][j], dp[k-1][i-strs[k-1].count(0)][j-strs[k-1].count(1)]
    int findMaxForm(vector<string>& strs, int m, int n) {
        //vector<vector<vector<int>>> dp(strs.size()+1, vector<vector<int>>(m + 1, vector<int>(n + 1, 0)));
        int dp[601][101][101] = {0};
        //dp[0][count(begin(strs[0]), end(strs[0]), '0')][count(begin(strs[0]), end(strs[0]), '1')] = 1;
        for (int k = 1; k <= strs.size(); k++) {
            int zeros = count(begin(strs[k-1]), end(strs[k-1]), '0');
            int ones = count(begin(strs[k-1]), end(strs[k-1]), '1');
            for (int i = 0; i <= m; i++) {
                for (int j = 0; j <= n; j++) {
                    dp[k][i][j] = max(dp[k - 1][i][j], i - zeros >= 0 && j - ones >= 0 ?
                                      dp[k - 1][i - zeros][j - ones] + 1 : dp[k - 1][i][j]);
                }
            }
        }
        return dp[strs.size()][m][n];
    }
};

1. 空间压缩

class Solution {
public:
    //0-1背包的二维费用问题
    //dp[i][j]: 考虑strs里[0,k-1]范围的字符串,i个0和j个1最多能够组多少个字符串
    //dp[i][j] = max of dp[i][j], dp[i-strs[k-1].count(0)][j-strs[k-1].count(1)]
    int findMaxForm(vector<string>& strs, int m, int n) {
        //vector<vector<vector<int>>> dp(strs.size()+1, vector<vector<int>>(m + 1, vector<int>(n + 1, 0)));
        int dp[101][101] = {0};
        for (int k = 1; k <= strs.size(); k++) {
            int zeros = count(begin(strs[k-1]), end(strs[k-1]), '0');
            int ones = count(begin(strs[k-1]), end(strs[k-1]), '1');
            for (int i = m; i >= zeros; i--) {
                for (int j = n; j >= ones; j--) {
                    dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1);
                }
            }
        }
        return dp[m][n];
    }
};

377. Combination Sum IV

注意溢出问题,使用了unsigned long long

1. 动态规划二维数组

二维数组实际上是多余的方式,实际转移方程和数组范围没有关系:
dp[i] = dp[i-nums[0]] + dp[i-nums[1]] + dp[i-nums[2]] + … + dp[i-nums[size-1]], (i-nums[j] >= 0)

class Solution {
public:
    //dp[i][j]: 前i个元素能够组成和为j的组合数量
    //dp[i][j] = dp[j-nums[0]] + dp[j-nums[1]] + dp[j-nums[2]] + ... + dp[j-nums[size-1]], (j-nums[k] >= 0)
    int combinationSum4(vector<int>& nums, int target) {
        vector<vector<unsigned long long>> dp(nums.size() + 1, vector<unsigned long long>(target+1, 0));
        for(int i = 1; i <= nums.size(); i++){
            dp[i][0] = 1;
        }
        for(int i = 1; i <= nums.size(); i++){
            for(int j = 0; j <= target; j++){
                for(int k = 0; k < i; k++){
                    if(j-nums[k] >= 0)
                        dp[i][j] += dp[i][j-nums[k]];
                }
            }
        }
        return dp[nums.size()][target];
    }
};

2. 动态规划一维数组

class Solution {
public:
    //dp[i]: 能够组成数量i的组合的数量
    //dp[i] = dp[i-nums[0]] + dp[i-nums[1]] + dp[i-nums[2]] + ... + dp[i-nums[size-1]], (i-nums[j] >= 0)
    //dp[0] = 1, 初始化,方便dp[1]以及后续问题的求解
    int combinationSum4(vector<int>& nums, int target) {
        if(nums.empty())
            if(target == 0)
                return 1;
            else
                return 0;
        vector<unsigned long long> dp(target+1, 0);
        dp[0] = 1;
        for(int i = 1; i <= target; i++){
            for(int j = 0; j < nums.size(); j++){
                if(i-nums[j] >= 0)
                    dp[i] += dp[i-nums[j]];
            }
        }
        return dp[target];
    }
};

3.记忆化递归

在这里插入图片描述

class Solution {
public:
    //memo[i]: 能够组成数量i的组合的数量combinationSum(nums, i)
    //memo[i] = combinationSum(i-nums[0]) + combinationSum(i-nums[1]) + ... + combinationSum(i-nums[j]), (i-nums[j] >= 0)
    //memo[0] = 1, 初始化,方便memo[1]以及后续问题的求解
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> memo(target + 1, -1);
//<<<<<<<<<<<<<<<<<
		//target==0时,不需要任何组合就有解
        memo[0] = 1;
//>>>>>>>>>>>>>>>>>
        if(nums.empty() && target != 0){
            return 0;
        }
        combinationSum(nums, target, memo);
        return memo[target];
    }
private:
    int combinationSum(vector<int>& nums, int target, vector<int>& memo) {
//        if(target <= 0){
//            if(target == 0)
//               return 1;
//            return 0;
//       } 
//<<<<<<<<<<<<<<<<<
        if(target < 0)
            return 0;
//>>>>>>>>>>>>>>>>>               
        if(memo[target] != -1){
            return memo[target];
        }
//<<<<<<<<<<<<<<<<<
        int ret = 0;
//>>>>>>>>>>>>>>>>>
        for(int j = 0; j < nums.size(); j++){
//            if(memo[target] == -1)
//                memo[target] = combinationSum(nums, target - nums[j], memo);
//            else
//                memo[target] += combinationSum(nums, target - nums[j], memo);
//<<<<<<<<<<<<<<<<<
            ret += combinationSum(nums, target - nums[j], memo);
//>>>>>>>>>>>>>>>>>
        }
        return memo[target] = ret;
    }
};

4.无返回的递归(超时)

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        ret = 0;
        combinationSum(nums, target);
        return ret;
    }
private:
    void combinationSum(vector<int>& nums, int target) {
        if(target <= 0){
            if(target == 0)
                ret += 1;
            return;
        }
        for(int j = 0; j < nums.size(); j++){
            combinationSum(nums, target - nums[j]);
        }
        return;
    }
    int ret;
};

343. Integer Break

1. 动态规划

class Solution {
public:
    //dp[i]: 和为i时相互之间的最大乘积
    //dp[i] = max(max(j*(i-j), j*dp[i-j]), dp[i]), j -> [1, i);
    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(max(j*(i-j), j*dp[i-j]), dp[i]);
            }
        }
        return dp[n];
    }
};

2. 记忆化递归

暴力解法: 递归,回溯遍历所有分割的可能性。O(2^n)
在这里插入图片描述

class Solution {
public:
    int integerBreak(int n) {
        vector<int> memo(n+1, -1);
        return helper(n, memo);
    }
    int helper(int n, vector<int>& memo) {
        if(n == 1)
            return 1;
        if(memo[n] != -1)
            return memo[n];

        int maxProduct = -1;
        for(int i = 1; i < n; i++){
            maxProduct = max(max(i*(n-i), i*helper(n-i, memo)), maxProduct);
        }
        memo[n] = maxProduct;
        return memo[n];
    }
};

279. Perfect Squares

1.动态规划

DP 有点类似于背包问题中的完全背包问题

需要明确的是n值越大,最优解趋向于利用j中更大的平方数来划分出子问题
在这里插入图片描述
应该需要优化一下时间效率
在这里插入图片描述

class Solution {
public:
    //完全背包问题?
    //dp[i]和为i的时候,需要的最少的完全平方数的个数
    //dp[i] = dp[i-j*j] + 1(j是子问题削减掉的完全平方数)
    int numSquares(int n) {
    int dp[n + 1] = {0};
    //初始化, 用最差的情况来初始化,比如n==12, 那么最差的情况是12个1相加
    for(int i = 0; i <=n ; i++){
        dp[i] = i;
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j*j <= i; j++){
            dp[i] = min(dp[i], dp[i-j*j] + 1);
        }
        cout << dp[i] <<endl;
    }
    return dp[n];
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值