赞
踩
解题步骤:
题目解析
我们对题目给的公式进行转化:
观察公式可以看到,如果要求第 i 个泰波那切数的话,我们只需知道前 i-1、i-2、i-3 的值即可。
算法原理
dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
编写代码
class Solution { public: int tribonacci(int n) { // 1、建表 vector<int> dp(n+1); // 2、初始化 dp[0] = 0, dp[1] = dp[2] = 1; // 3、填表 for(int i = 3; i <= n; ++i) { dp[i] = dp[i-1] + dp[i-2] + dp[i-3]; } // 4、返回值 return dp[n]; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
题目解析:小孩最多一次跳三步,假设要跳到第n级台阶,那最后跳到第n级台阶时的那一步一定是下面三种情况之一:
所以想求跳到第n级台阶有几种方法的话,我们只需知道跳到第 n-1、n-2 和 n-3 台阶时各自有几种方法,然后把它们相加起来即可。
PS:计算时需要注意取模
算法原理
代码实现
class Solution { public: int waysToStep(int n) { // 1、处理特殊情况 if (n == 1) return 1; if (n == 2) return 2; if (n == 3) return 4; // 2、创建dp表 vector<int> dp(n + 1); // 3、初始化 dp[1] = 1; dp[2] = 2; dp[3] = 4; // 4、填表 for (size_t i = 4; i <= n; ++i) dp[i] = ((dp[i - 1] + dp[i - 2]) % 1000000007 + dp[i - 3]) % 1000000007; // 5、返回值 return dp[n]; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
题目解析
代码实现
class Solution { public: int minCostClimbingStairs(vector<int>& cost) { int n = cost.size(); // 1、建表 && 初始化 vector<int> dp(n + 1); // 2、填表 for(int i = 2; i <= n; ++i) dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]); // 3、返回值 return dp[n]; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
状态表示:dp[i] 表示从i位置出发,到达楼顶,此时的最小花费
状态转移方程
初始化:dp[n-1] = cost[n-1]、dp[n-2] = cost[n-2]
填表顺序:从右往左
返回值:min(dp[0], dp[1])
代码实现
class Solution { public: int minCostClimbingStairs(vector<int>& cost) { int n = cost.size(); vector<int> dp(n); dp[n - 1] = cost[n - 1]; dp[n - 2] = cost[n - 2]; for (int i = n - 3; i >= 0; --i) dp[i] = cost[i] + min(dp[i + 1], dp[i + 2]); return min(dp[0], dp[1]); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
题目解析
输入是一个只含有数字的非空字符串,要求返回所有可能存在的解码方式(注意单独一个0,和0x为非法数字,不能用来解码)
算法原理
状态表示:dp[i] 代表 以 i 位置为结尾时,解码方法的总数
状态转移方程:根据最近的一步,划分问题
初始化:情况转移方程情况复杂的话,我们可以考虑dp表多开一个空间这样方便我们后继填表,这时要注意多开出来的那块空间初,他始化的值要能够让我们的填表逻辑正确。
填表顺序:从左往右
返回值:dp[n]
代码实现
class Solution { public: int numDecodings(string s) { // 1、建表 size_t n = s.size(); vector<int> dp(n + 1); // 2、初始化 dp[0] = 1; dp[1] = s[0] != '0'; // 3、填表 for (size_t i = 2; i <= n; ++i) { if (s[i - 1] != '0') dp[i] += dp[i - 1]; if (atoi(s.substr(i - 2, 2).c_str()) >= 10 && atoi(s.substr(i - 2, 2).c_str()) <= 26) dp[i] += dp[i - 2]; } // 4、返回值 return dp[n]; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
状态表示:dp[ i ][ j ]的含义是:走到 (i, j) 位置,一共有多少种走法
状态转移方程:根据最近的一步去划分问题
初始化:为了方便填表,我们给dp表多开了一行和一列
填表顺序:从上往下填写每一行,每一行再从左往右填写
返回值:dp[m][n]
代码实现
class Solution { public: int uniquePaths(int m, int n) { // 1、建表 // 2、初始化 // 3、填表 // 4、返回值 vector<vector<int>> dp(m + 1, vector<int>(n + 1)); dp[1][0] = 1; for (size_t i = 1; i <= m; ++i) for (size_t j = 1; j <= n; ++j) dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
题目解析
这道题的思路和不同路径I 是一样的,只不过我们在填表时,如果遇到了障碍物,那就说明这个位置是无法走到的,我们把这个位置的路径数置为0即可
算法原理
状态表示:dp[i][j] 表示到达 (i, j) 位置时,有多少种路径
状态转移方程
初始化:为了方便后续填表,我们创建的 dp 表要比题目给的矩阵多开一行和多开一列,初始化时要注意以下两点:
填表顺序:从上往下填写每一行,每一行再从左往右填写
返回值:dp[m][n]
代码实现
class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& ob) { // 1、建表 size_t m = ob.size(); size_t n = ob[0].size(); vector<vector<int>> dp(m + 1, vector<int>(n)); // 2、初始化 dp[0][1] = 1; // 3、填表 for (size_t i = 1; i <= m; ++i) for (size_t j = 1; j <= n; ++j) if (ob[i - 1][j - 1] != 1) dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; // 4、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
代码编写
class Solution { public: // 1、创建 dp 表 // 2、初始化 // 3、填表 // 4、返回值 int maxValue(vector<vector<int>>& grid) { size_t m = grid.size(), n = grid[0].size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); for (size_t i = 1; i <= m; ++i) for (size_t j = 1; j <= n; ++j) dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1]; return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
状态表示(根据经验+题目要求,dp[i][j]表示以 (i, j) 位置为 结尾/起点 时,xxxxxxx…)
状态转移方程
初始化(为了保证在填表的时候不越界):
填表顺序:每一个元素的值只受最靠近他的上面位置的三个元素的值所影响,所以我们只需保证从上往下填表即可。
返回值:最后一行的最小值
代码编写
class Solution { public: int minFallingPathSum(vector<vector<int>>& matrix) { // 1、建表 size_t n = matrix.size(); vector<vector<int>> dp(n + 1, vector<int>(n + 2, INT_MAX)); // 2、初始化 for (size_t j = 0; j < n + 2; ++j) dp[0][j] = 0; // 3、填表 for (size_t i = 1; i < n + 1; ++i) for (size_t j = 1; j < n + 1; ++j) dp[i][j] = min(min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i - 1][j + 1]) + matrix[i-1][j-1]; // 4、返回值 int ret = INT_MAX; for (size_t j = 1; j < n + 1; ++j) if (dp[n][j] < ret) ret = dp[n][j]; return ret; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
状态表示(题目 + 经验要求),通常是:以某位置为 结尾/起点 时,巴拉巴拉巴拉…
状态转移方程
初始化(保证填表的时候不越界)
填表顺序:从上往下,每一行再从左往右
返回值:dp[m][n]
代码编写
class Solution { public: int minPathSum(vector<vector<int>>& grid) { // 1、创建 dp 表 size_t m = grid.size(), n = grid[0].size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX)); // 2、初始化 dp[0][1] = dp[1][0] = 0; // 3、填表 for (size_t i = 1; i < m + 1; ++i) for (size_t j = 1; j < n + 1; ++j) dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i - 1][j - 1]; // 4、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
代码编写
class Solution { public: int calculateMinimumHP(vector<vector<int>>& dungeon) { // 建表 int m = dungeon.size(), n = dungeon[0].size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1, INT_MAX)); // 初始化 dp[m - 1][n] = dp[m][n - 1] = 1; // 填表 for (int i = m - 1; i >= 0; --i) { for (int j = n - 1; j >= 0; --j) { dp[i][j] = min(dp[i][j + 1], dp[i + 1][j]) - dungeon[i][j]; dp[i][j] = max(1, dp[i][j]); } } // 返回值 return dp[0][0]; } }; /* - 时间复杂度:O(m*n) - 空间复杂度:O(m*n) */
题目解析
算法原理
代码编写
class Solution { public: int massage(vector<int>& nums) { // 1、建表 int n = nums.size(); if (n == 0) return 0; vector<int> f(n), g(n); // 2、初始化 f[0] = nums[0]; g[0] = 0; // 3、填表 for (int i = 1; i < n; ++i) { f[i] = g[i - 1] + nums[i]; g[i] = max(f[i - 1], g[i - 1]); } // 4、返回值 return max(f[n - 1], g[n - 1]); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int rob(vector<int>& nums) { // 建表 int n = nums.size(); vector<int> f(n), g(n); // 初始化 f[0] = nums[0]; // 填表 for (int i = 1; i < n; ++i) { f[i] = g[i - 1] + nums[i]; g[i] = max(f[i - 1], g[i - 1]); } // 返回值 return max(f[n - 1], g[n - 1]); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int rob(vector<int>& nums) { int n = nums.size(); return max(nums[0] + _rob(nums, 2, n - 2), _rob(nums, 1, n - 1)); } private: int _rob(vector<int>& nums, int left, int right) { if (left > right) return 0; // 建表 int n = right - left + 1; vector<int> f(n), g(n); // 初始化 f[0] = nums[left]; // 填表 for (int i = 1; i < n; ++i) { f[i] = g[i - 1] + nums[left + i]; g[i] = max(f[i - 1], g[i - 1]); } // 返回值 return max(f[n - 1], g[n - 1]); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int deleteAndEarn(vector<int>& nums) { // 1、建表 vector<int> f(40001); vector<int> g(40001); vector<int> count(40001); // 2、初始化 for(const auto e : nums) count[e] += e; // 3、填表 for(int i = 1; i < 40001; ++i) { f[i] = count[i] + g[i-1]; g[i] = max(f[i-1], g[i-1]); } // 4、返回值 return max(f[40000], g[40000]); } }; /* - 时间复杂度:O(n + 40001) - 空间复杂度:O(1) */
算法原理
min(dp[n][0], dp[n][1], dp[n][2])
代码编写
class Solution { public: int minCost(vector<vector<int>>& costs) { // 1、建表并初始化 int n = costs.size(); vector<vector<int>> dp(n + 1, vector<int>(3)); // 2、填表 for(int i = 1; i <= n; ++i) { dp[i][0] = costs[i-1][0] + min(dp[i-1][1], dp[i-1][2]); dp[i][1] = costs[i-1][1] + min(dp[i-1][0], dp[i-1][2]); dp[i][2] = costs[i-1][2] + min(dp[i-1][0], dp[i-1][1]); } // 3、返回值 return min(dp[n][0], min(dp[n][1], dp[n][2])); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
状态表示
状态转移方程
初始化:根据状态转移方程可知:想要得到某天结束时,该天三种状态(持有、不持有且处于冷冻期、不持有但不处于冷冻期)各自的最大利润,则需要知道前一天的三种状态的值,所以我们只需要初始化好第一天三种状态的结果,然后填表时从第二天开始填即可
填表顺序:从上往下填,填写每一天的三种状态各自的最大利润
返回值
代码编写
class Solution { public: int maxProfit(vector<int>& prices) { // 1、建表 int n = prices.size(); vector<vector<int>> dp(n, vector<int>(3)); // 2、初始化 dp[0][0] = -prices[0]; // 3、填表 for (int i = 1; i < n; ++i) { dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 1][2]); dp[i][2] = dp[i - 1][0] + prices[i]; } // 4、返回值 return max(dp[n - 1][1], dp[n - 1][2]); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
状态表示
状态转移方程
初始化:f[0] = -prices[i]、g[0] = 0
填表顺序:从左往右,两张表一起填
返回值:最终返回值的话,就不考虑 f[n-1] 了,因为最后一天还持有股票的话:要么就是当天新买的,要么就是之前买的,但是一直没有卖出。这两种情况都不可能是最大利润,所以直接返回 g[n-1] 即可。
代码编写
class Solution { public: int maxProfit(vector<int>& prices, int fee) { // 1、建表 int n = prices.size(); vector<int> f(n), g(n); // 2、初始化 f[0] = -prices[0]; // 3、填表 for(int i = 1; i < n; ++i) { f[i] = max(f[i-1], g[i-1] - prices[i]); g[i] = max(g[i-1], f[i-1] + prices[i] - fee); } // 4、返回值 return g[n-1]; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
状态表示
状态转移方程
初始化
填表顺序:从 i=1 开始,从下往上填写每一行,每一行从左往右,两张表一起填
返回值:找到并返回 g 表最后一行的最大值(可能是零笔交易出最大值,也可能是一笔或两笔)
代码编写
class Solution { public: const int INF = 0x3f3f3f3f; int maxProfit(vector<int>& prices) { // 1、建表 int n = prices.size(); vector<vector<int>> f(n, vector<int>(3, -INF)); auto g(f); // 2、初始化 f[0][0] = -prices[0], g[0][0] = 0; // 3、填表 for (int i = 1; i < n; ++i) { for (int j = 0; j < 3; ++j) { f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]); if (j - 1 < 0) g[i][j] = g[i - 1][j]; else g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i]); } } // 4、返回值 int ret = -INF; for (int j = 0; j < 3; ++j) { if (g[n - 1][j] > ret) { ret = g[n - 1][j]; } } return ret; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: const int INF = 0x3f3f3f3f; int maxProfit(int k, vector<int>& prices) { // 1、建表 int n = prices.size(); vector<vector<int>> f(n, vector<int>(k + 1, -INF)); auto g(f); // 2、初始化 f[0][0] = -prices[0], g[0][0] = 0; // 3、填表 for (int i = 1; i < n; ++i) { for (int j = 0; j < k + 1; ++j) { f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]); if (j - 1 < 0) g[i][j] = g[i - 1][j]; else g[i][j] = max(g[i - 1][j], f[i - 1][j - 1] + prices[i]); } } // 4、返回值 int ret = -INF; for (int j = 0; j < k + 1; ++j) { if (g[n - 1][j] > ret) { ret = g[n - 1][j]; } } return ret; } }; /* - 时间复杂度:O(n*k) - 空间复杂度:O(n*k) */
算法原理
代码编写
class Solution { public: const int INF = 0x3f3f3f3f; int maxSubArray(vector<int>& nums) { // 1、建表 int n = nums.size(); vector<int> dp(n + 1); // 2、初始化 dp[0] = -INF; // 2、填表 for (int i = 1; i <= n; ++i) { dp[i] = max(nums[i-1], dp[i - 1] + nums[i-1]); } // 3、返回值 return *max_element(dp.begin(), dp.end()); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
思路分析
算法原理
状态表示
状态转移方程
初始化
填表顺序:从左往右,两张表一起填
返回值
算法原理
代码编写
class Solution { public: int maxProduct(vector<int>& nums) { // 1、建表 int n = nums.size(); vector<int> f(n + 1), g(n + 1); // 2、初始化 f[0] = g[0] = 1; // 3、填表 for (int i = 1; i <= n; ++i) { f[i] = max(nums[i - 1], max(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1])); g[i] = min(nums[i - 1], min(f[i - 1] * nums[i - 1], g[i - 1] * nums[i - 1])); } // 4、返回值 return *max_element(f.begin() + 1, f.end()); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int getMaxLen(vector<int>& nums) { // 1、建表 && 初始化 int n = nums.size(); vector<int> f(n + 1), g(n + 1); // 2、填表 for(int i = 1; i <= n; ++i) { if(nums[i - 1] > 0) f[i] = f[i - 1] + 1; else f[i] = (g[i - 1] == 0 || nums[i - 1] == 0) ? 0 : g[i - 1] + 1; if(nums[i - 1] >= 0) g[i] = (g[i - 1] == 0 || nums[i - 1] == 0) ? 0 : g[i - 1] + 1; else g[i] = f[i - 1] + 1; } // 3、返回值 return *max_element(f.begin() + 1, f.end()); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int numberOfArithmeticSlices(vector<int>& nums) { // 1、建表 和 初始化 int n = nums.size(); vector<int> dp(n); // 2、填表 int ret = 0; for(int i = 2; i < n; ++i) { nums[i - 1] - nums[i - 2] == nums[i] - nums[i - 1] ? dp[i] = dp[i - 1] + 1 : dp[i] = 0; ret += dp[i]; } // 3、返回值 return ret; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int maxTurbulenceSize(vector<int>& arr) { // 1、建表 && 初始化 int n = arr.size(); vector<int> f(n, 1), g(n, 1); // 2、填表 for (int i = 1; i < n; ++i) { if (arr[i - 1] < arr[i]) f[i] = g[i - 1] + 1; if (arr[i - 1] > arr[i]) g[i] = f[i - 1] + 1; } // 3、返回值 return max(*max_element(f.begin(), f.end()), *max_element(g.begin(), g.end())); } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: bool wordBreak(string s, vector<string>& wordDict) { // 1、建表 int n = s.size(); vector<int> dp(n + 1); // 2、初始化 dp[0] = true; s = ' ' + s; // 3、填表 for(int i = 1; i <= n; ++i) { for(int j = i; j >= 1; --j) { if(dp[j - 1] && find(wordDict.begin(), wordDict.end(), s.substr(j, i - j + 1)) != wordDict.end()) { dp[i] = true; break; } } } // 4、返回值 return dp[n]; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n^2) */
算法原理
代码编写
class Solution { public: int findSubstringInWraproundString(string s) { // 1、建表 && 初始化 int n = s.size(); vector<int> dp(n, 1); // 2、填表 for(int i = 1; i < n; ++i) if(s[i - 1] + 1 == s[i] || (s[i - 1] == 'z' && s[i] == 'a')) dp[i] += dp[i - 1]; // 3、返回值 vector<int> count(26); for(int i = 0; i < n; ++i) count[s[i] - 'a'] = max(count[s[i] - 'a'], dp[i]); int ret = 0; for(const auto e : count) ret += e; return ret; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
概念 | 定义 | 要求 | 范围 |
---|---|---|---|
子数组 | 一个或连续多个数组中的元素组成一个子数组(子数组最少包含一个元素) | 必须要相邻 | 更小 |
子序列 | 子序列就是在原来序列中找出一部分组成的序列(子序列不一定连续) | 必须要保证相对顺序 | 更大 |
算法原理:
代码编写
class Solution { public: int lengthOfLIS(vector<int>& nums) { // 1、建表 && 初始化 int n = nums.size(); vector<int> dp(n, 1); // 2、填表 for(int i = 1; i < n; ++i) for(int j = 0; j < i; ++j) if(nums[j] < nums[i]) dp[i] = max(dp[i], 1 + dp[j]); // 3、返回值 return *max_element(dp.begin(), dp.end()); } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n) */
算法原理
状态表示
状态转移方程
初始化:把 f、g 表中的元素全都初始化为 1,表示最基本的情况,即只有一个数字构成的摆动序列。
填表顺序:从左往右,两张表一起填
返回值:两张表中所有元素的最大值
代码编写
class Solution { public: int wiggleMaxLength(vector<int>& nums) { // 1、建表 && 初始化 int n = nums.size(); vector<int> f(n, 1), g(n, 1); // 2、填表 for(int i = 1; i < n; ++i) { for(int j = 0; j < i; ++j) { if(nums[j] < nums[i]) f[i] = max(f[i], g[j] + 1); if(nums[j] > nums[i]) g[i] = max(g[i], f[j] + 1); } } // 3、返回值 return max(*max_element(f.begin(), f.end()), *max_element(g.begin(), g.end())); } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n) */
算法原理
在讲解算法原理之前,我们先看一个小 demo:
了解了上面算法的思想后,回到本题:
代码编写
class Solution { public: int findNumberOfLIS(vector<int>& nums) { // 1、建表 && 初始化 int n = nums.size(); vector<int> len(n, 1), count(n, 1); // 2、填表 int retLen = 1, retCount = 1; for(int i = 1; i < n; ++i) { for(int j = 0; j < i; ++j) { if(nums[j] < nums[i]) { if(len[j] + 1 == len[i]) count[i] += count[j]; else if(len[j] + 1 > len[i]) len[i] = len[j] + 1, count[i] = count[j]; } } if(len[i] > retLen) retLen = len[i], retCount = count[i]; else if(len[i] == retLen) retCount += count[i]; } // 返回值 return retCount; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int findLongestChain(vector<vector<int>>& pairs) { // 1、预处理 sort(pairs.begin(), pairs.end()); // 2、建表 int n = pairs.size(); vector<int> dp(n, 1); // 3、填表 for(int i = 1; i < n; ++i) for(int j = 0; j < i; ++j) if(pairs[j][1] < pairs[i][0]) dp[i] = max(dp[i], dp[j] + 1); // 4、返回值 return *max_element(dp.begin(), dp.end()); } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n) */
算法原理
状态表示
状态转移方程
初始化
填表顺序:从左往右
返回值:dp 表中的最大值,注意 dp 表是作为哈希表的 value
代码编写
class Solution { public: int longestSubsequence(vector<int>& arr, int difference) { // 1、建表 unordered_map<int, int> hash; // 2、初始化 hash[arr[0]] = 1; // 3、填表 int ret = INT_MIN; for (int i = 1; i < arr.size(); ++i) { hash[arr[i]] = hash[arr[i] - difference] + 1; ret = max(ret, hash[arr[i]]); } // 4、返回值 return ret; } }; /* - 时间复杂度:O(n) - 空间复杂度:O(n) */
算法原理
代码编写
class Solution { public: int lenLongestFibSubseq(vector<int>& arr) { // 1、建表 int n = arr.size(); vector<vector<int>> dp(n, vector<int>(n, 2)); // 2、初始化 unordered_map<int, int> hash; for(int i = 0; i < n; ++i) hash[arr[i]] = i; // 3、填表 int ret = INT_MIN; for(int j = 0; j < n; ++j) { for(int i = 0; i < j; ++i) { int a = arr[j] - arr[i]; if(hash.count(a) && a < arr[i]) dp[i][j] = dp[hash[a]][i] + 1; ret = max(ret, dp[i][j]); } } // 4、返回值 return ret == 2 ? 0 : ret; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
代码编写
class Solution { public: int longestArithSeqLength(vector<int>& nums) { // 0、优化 unordered_map<int, int> hash; hash[nums[0]] = 0; // 1、建表 && 初始化 int n = nums.size(); vector<vector<int>> dp(n, vector<int>(n, 2)); // 2、填表 int ret = 2; for(int i = 1; i < n - 1; ++i) // 固定倒数第二个数 { for(int j = i + 1; j < n; ++j) // 枚举倒数第一个数 { int a = 2 * nums[i] - nums[j]; if(hash.count(a)) dp[i][j] = dp[hash[a]][i] + 1; ret = max(ret, dp[i][j]); } hash[nums[i]] = i; // 更新 nums[i] 的下标 } // 3、返回值 return ret; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
代码编写
class Solution { public: int numberOfArithmeticSlices(vector<int>& nums) { // 0、优化 unordered_map<long long, vector<int>> hash; hash[nums[0]].push_back(0); // 1、建表 && 初始化 int n = nums.size(); vector<vector<int>> dp(n, vector<int>(n)); // 2、填表 int sum = 0; //记录dp表中所有元素之和 for(int i = 1; i < n - 1; ++i) { for(int j = i + 1; j < n; ++j) { long long a = 2 * (long long)nums[i] - nums[j]; if(hash.count(a)) for(const auto e : hash[a]) dp[i][j] += dp[e][i] + 1; sum += dp[i][j]; } hash[nums[i]].push_back(i); } // 3、返回值 return sum; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
状态表示:dp[i][j] 表示 s 字符串中,下标范围 [i, j] 的子串,它是否构成回文串
状态转移方程
初始化:创建一个 n*n 的 dp 表即可,存储元素全是 bool 类型的 false
填表顺序:从下往上填写每一行,每一行再从左往右填写
返回值: dp 表里面 true 的个数
代码编写
class Solution { public: int countSubstrings(string s) { int count = 0; size_t n = s.size(); vector<vector<bool>> dp(n, vector<bool>(n)); for (int i = n - 1; i >= 0; --i) for (int j = i; j < n; ++j) if (s[i] == s[j] && (i == j || i + 1 == j || dp[i + 1][j - 1])) dp[i][j] = true, ++count; return count; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
代码编写
class Solution { public: string longestPalindrome(string s) { // 1、建表 size_t n = s.size(); // 用来存储最长子串的起始下标和长度,到最后可以使用 substr 还原出该子串 pair<size_t, size_t> pr(n - 1, 1); // dp 表用来存储所有字串的回文情况 vector<vector<bool>> dp(n, vector<bool>(n)); // 2、填表 for(int i = n - 1; i >= 0; --i) { for(int j = i; j < n; ++j) { if(s[i] == s[j]) dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true; if(dp[i][j] && j - i + 1 > pr.second) pr.first = i, pr.second = j - i + 1; } } // 3、返回值 return s.substr(pr.first, pr.second); } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
代码编写
class Solution { public: bool checkPartitioning(string s) { // 1、用 dp 把所有字串是否是回文预处理一下 int n = s.size(); vector<vector<bool>> dp(n, vector<bool>(n)); for(int i = n - 1; i >= 0; --i) for(int j = i; j < n; ++j) if(s[i] == s[j]) dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true; // 2、枚举所有第二个字符串的起始位置和结束位置 for(int i = 1; i < n - 1; ++i) for(int j = i; j < n - 1; ++j) if(dp[0][i - 1] && dp[i][j] && dp[j + 1][n - 1]) return true; return false; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
状态表示:dp[i] 表示: [0, i] 区间的子字符串,最少的分割次数
状态转移方程
初始化:在更新 dp[i] 时,需要比较原 dp[i] 和 dp[j - 1] + 1,二者的最小值。我们把 dp 表中的元素一开始都初始化为 INT_MAX,主要是为了防止第一次更新 dp[i] 时,不让初始的 dp[i] 去影响我们的更新结果。
填表顺序:从左往右
返回值:dp[n - 1]
代码编写
class Solution { public: int minCut(string s) { // 1、在 isPal 表中统计所有字串的回文信息 int n = s.size(); vector<vector<bool>> isPal(n, vector<bool>(n)); for(int i = n - 1; i >= 0; --i) for(int j = i; j < n; ++j) if(s[i] == s[j]) isPal[i][j] = !(i + 1 < j) || isPal[i + 1][j - 1]; // 2、创建并填写 dp 表 vector<int> dp(n, INT_MAX); for(int i = 0; i < n; ++i) { if(isPal[0][i]) { dp[i] = 0; } else { for(int j = 1; j <= i; ++j) { if(isPal[j][i]) dp[i] = min(dp[i], dp[j-1] + 1); } } } // 3、返回值 return dp[n - 1]; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
代码编写
class Solution { public: int longestPalindromeSubseq(string s) { // 1、建表 int n = s.size(); vector<vector<int>> dp(n, vector<int>(n)); // 2、填表 for(int i = n - 1; i >= 0; --i) for(int j = i; j < n; ++j) { if(s[i] == s[j]) dp[i][j] = i + 1 >= j ? (j - i + 1) : dp[i + 1][j - 1] + 2; else dp[i][j] = max(dp[i][j - 1], dp[i + 1][j]); } // 3、返回值 return dp[0][n - 1]; } };
算法原理
代码编写
class Solution { public: int minInsertions(string s) { // 1、建表 int n = s.size(); vector<vector<int>> dp(n, vector<int>(n)); // 2、填表 for(int i = n - 1; i >= 0; --i) for(int j = i + 1; j < n; ++j) if(s[i] == s[j]) dp[i][j] = dp[i+1][j-1]; else dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 1; // 3、返回值 return dp[0][n-1]; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
状态表示
状态转移方程
初始化
填表顺序
返回值
代码编写
class Solution { public: int longestCommonSubsequence(string s1, string s2) { // 1、建表 int m = s1.size(), n = s2.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); // 2、初始化 s1 = ' ' + s1; s2 = ' ' + s2; // 3、填表 for(int i = 1; i <= m; ++i) for(int j = 1; j <= n; ++j) if(s1[i] == s2[j]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); // 3、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
状态表示
状态转移方程
初始化
填表顺序
返回值
代码编写
class Solution { public: int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) { // 1、建表 int m = nums1.size(), n = nums2.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); // 2、初始化 nums1.insert(nums1.begin(), 0); nums2.insert(nums2.begin(), 0); // 3、填表 for(int i = 1; i <= m; ++i) for(int j = 1; j <= n; ++j) if(nums1[i] == nums2[j]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); // 4、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
算法原理
状态表示
状态转移方程
初始化
填表顺序
返回值:dp[m][n],其中 m 为 t 字符串的长度,n 为 s 字符串的长度
代码编写
class Solution { public: int numDistinct(string s, string t) { // 1、建表 int m = t.size(), n = s.size(); vector<vector<long long>> dp(m + 1, vector<long long>(n + 1)); // 2、初始化 s = ' ' + s; t = ' ' + t; for(int j = 0; j <= n; ++j) dp[0][j] = 1; // 3、填表 for(int i = 1; i <= m; ++i) for(int j = 1; j <= n; ++j) if(t[i] == s[j]) dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - 1]) % (10000000000 + 7); else dp[i][j] = dp[i][j - 1] % (10000000000 + 7); // 4、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
状态表示
状态转移方程
初始化
填表顺序
返回值:dp[m][n],其中m为字符串s的长度,n为字符串p的长度
代码编写
class Solution { public: bool isMatch(string s, string p) { // 1、建表 int m = s.size(), n = p.size(); vector<vector<bool>> dp(m + 1, vector<bool>(n + 1)); // 2、初始化 s = ' ' + s; p = ' ' + p; for(int j = 0; j <= n; ++j) { if(j == 0) dp[0][j] = true; else if(p[j] == '*' && dp[0][j - 1] == true) dp[0][j] = true; } // 3、填表 for(int i = 1; i <= m; ++i) { for(int j = 1; j <= n; ++j) { if(p[j] == '*') // p[j] == '*' dp[i][j] = dp[i - 1][j] || dp[i][j - 1]; else // p[j] == '?' 或 普通字符 if((p[j] == '?' || s[i] == p[j]) && dp[i - 1][j - 1]) dp[i][j] = true; } } // 4、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
状态表示
状态转移方程
初始化
填表顺序
返回值:dp[m][n],m为s字符串的长度,n为p字符串的长度
代码编写
class Solution { public: bool isMatch(string s, string p) { // 1、建表 int m = s.size(), n = p.size(); vector<vector<bool>> dp(m + 1, vector<bool>(n + 1)); // 2、初始化 s = ' ' + s; p = ' ' + p; dp[0][0] = true; for(int j = 2; j <= n; j += 2) if(p[j] == '*') dp[0][j] = true; else break; // 3、填表 for(int i = 1; i <= m; ++i) for(int j = 1; j <= n; ++j) if(p[j] == '*') dp[i][j] = dp[i][j-2] || (p[j-1] == s[i] || p[j-1] == '.') && dp[i-1][j]; else dp[i][j] = (p[j] == s[i] || p[j] == '.') && dp[i-1][j-1]; // 4、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
预处理
状态表示
状态转移方程
初始化
填表顺序
返回值:dp[m][n],其中 m 为 s1 的长度,n 为 s2 的长度
代码编写
class Solution { public: bool isInterleave(string s1, string s2, string s3) { // 1、建表 int m = s1.size(), n = s2.size(); if(m + n != s3.size()) return false; vector<vector<bool>> dp(m + 1, vector<bool>(n + 1)); // 2、初始化 s1 = ' ' + s1; s2 = ' ' + s2; s3 = ' ' + s3; dp[0][0] = true; for(int i = 1; i <= m; ++i) // 初始化第一行 if(s1[i] == s3[i]) dp[i][0] = true; else break; for(int j = 1; j <= n; ++j) // 初始化第一列 if(s2[j] == s3[j]) dp[0][j] = true; else break; // 3、填表 for(int i = 1; i <= m; ++i) for(int j = 1; j <= n; ++j) dp[i][j] = (s1[i] == s3[i+j] && dp[i-1][j]) ||(s2[j] == s3[i+j] && dp[i][j-1]); // 4、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
分析:题目要求我们计算让两个子序列相同时,所需删除字符的 ASCll 最小和既然要删除的字符之和最小,那么留下的子序列之和就是最大,所以可以使用“正难则反”的思想,去计算两个字符串中所有公共子序列里面,ASCll 值最大的公共子序列是那个。
状态表示
状态转移方程
初始化
填表顺序
返回值
代码编写
class Solution { public: int minimumDeleteSum(string s1, string s2) { // 1、建表 int m = s1.size(), n = s2.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); // 2、初始化 s1 = ' ' + s1; s2 = ' ' + s2; // 3、填表 for(int i = 1; i <= m; ++i) for(int j = 1; j <= n; ++j) { int x1 = (s1[i] == s2[j] ? dp[i-1][j-1] + s1[i] : 0); int x2 = dp[i][j - 1]; int x3 = dp[i - 1][j]; dp[i][j] = max(max(x2, x3), x1); } // 4、返回值 int sum = 0; // 计算总和时不要把两个字符串最开头的空格给算进去 for(int i = 1; i <= m; ++i) sum += s1[i]; for(int j = 1; j <= n; ++j) sum += s2[j]; return sum - 2 * dp[m][n]; } }; /* - 时间复杂度:O(mn) - 空间复杂度:O(mn) */
算法原理
状态表示
状态转移方程
初始化
填表顺序
返回值:dp 表中的最大值
题目解析
算法原理
代码编写
#include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { // 1、数据输入 int n = 0; // 物品个数 int V = 0; // 背包容量 cin >> n >> V; // n 个物品的体积和价值表,其中第0个位置表示一个虚拟物品 // 它的体积和价值都为0,设置它的目的是为了下标和dp表对应 vector<int> v(n + 1), w(n + 1); for(int i = 1; i <= n; ++i) cin >> v[i] >> w[i]; // 2、创建 dp 表 vector<vector<int>> dp(n + 1, vector<int>(V + 1)); // 3、解决第一问 for(int i = 1; i <= n; ++i) for(int j = 1; j <= V; ++j) { dp[i][j] = dp[i - 1][j]; if(j - v[i] >= 0) dp[i][j] = max(dp[i][j], w[i] + dp[i - 1][j - v[i]]); } cout << dp[n][V] << endl; // 4、解决第二问 for(int j = 1; j <= V; ++j) dp[0][j] = -1; for(int i = 1; i <= n; ++i) for(int j = 1; j <= V; ++j) { dp[i][j] = dp[i - 1][j]; if(j - v[i] >= 0 && dp[i - 1][j - v[i]] != -1) dp[i][j] = max(dp[i][j], w[i] + dp[i - 1][j - v[i]]); } cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl; return 0; } /* - 时间复杂度:O(nV) - 空间复杂度:O(nV) */
优化
优化后代码如下:
#include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { // 1、数据输入 int n = 0; // 物品个数 int V = 0; // 背包容量 cin >> n >> V; // n 个物品的体积和价值表,其中第0个位置表示一个虚拟物品 // 它的体积和价值都为0,设置它的目的是为了下标和dp表对应 vector<int> v(n + 1), w(n + 1); for(int i = 1; i <= n; ++i) cin >> v[i] >> w[i]; // 2、创建 dp 表 vector<int> dp(V + 1); // 3、解决第一问 for(int i = 1; i <= n; ++i) for(int j = V; j - v[i] >= 0; --j) dp[j] = max(dp[j], w[i] + dp[j - v[i]]); cout << dp[V] << endl; // 4、解决第二问 for(int j = 1; j <= V; ++j) dp[j] = -1; for(int i = 1; i <= n; ++i) for(int j = V; j - v[i] >= 0; --j) { if(dp[j - v[i]] != -1) dp[j] = max(dp[j], w[i] + dp[j - v[i]]); } cout << (dp[V] == -1 ? 0 : dp[V]) << endl; return 0; } /* - 时间复杂度:O(nV) - 空间复杂度:O(V) */
算法原理
代码编写
class Solution { public: bool canPartition(vector<int>& nums) { // 1、建表 int n = nums.size(), sum = 0; for(const auto e : nums) sum += e; // 计算所有数字之和 if(sum % 2) return false; // 如果数组之和为奇数,则不可能分割成两个等和子集 vector<vector<bool>> dp(n + 1, vector<bool>(sum/2 + 1)); // 创建 dp 表 // 2、初始化 for(int i = 0; i <= n; ++i) dp[i][0] = true; // 3、填表(注意 dp 表下标和 nums 表下标的映射关系) for(int i = 1; i <= n; ++i) for(int j = 1; j <= sum/2; ++j) { dp[i][j] = dp[i - 1][j]; if(j - nums[i - 1] >= 0) dp[i][j] = dp[i][j] || dp[i-1][j - nums[i - 1]]; } // 4、返回值 return dp[n][sum/2]; } }; /* - 时间复杂度:O(n*sum) - 空间复杂度:O(n*sum) */ // 空间优化版本 // 1. 删除横坐标 // 2. 修改纵坐标遍历顺序 class Solution { public: bool canPartition(vector<int>& nums) { // 1、建表 int n = nums.size(), sum = 0; for(const auto e : nums) sum += e; // 计算所有数字之和 if(sum % 2) return false; // 如果数组之和为奇数,则不可能分割成两个等和子集 vector<bool> dp(sum/2 + 1); // 创建 dp 表 // 2、初始化 dp[0] = true; // 3、填表(注意 dp 表下标和 nums 表下标的映射关系) for(int i = 1; i <= n; ++i) for(int j = sum/2; j >= nums[i - 1]; --j) dp[j] = dp[j] || dp[j - nums[i - 1]]; // 4、返回值 return dp[sum/2]; } }; /* - 时间复杂度:O(n*sum) - 空间复杂度:O(sum) */
算法原理
代码编写
// 未优化版本 class Solution { public: int findTargetSumWays(vector<int>& nums, int target) { // 0、预处理 int n = nums.size(), sum = 0; for(const auto e : nums) sum += e; int aim = (sum + target) / 2; if(aim < 0 || (sum + target) % 2) return 0; // 1、建表 vector<vector<int>> dp(n + 1, vector<int>(aim + 1)); // 2、初始化 dp[0][0] = 1; // 3、填表 for(int i = 1; i <= n; ++i) for(int j = 0; j <= aim; ++j) { dp[i][j] = dp[i - 1][j]; if(j >= nums[i - 1]) dp[i][j] += dp[i - 1][j - nums[i - 1]]; } // 4、返回值 return dp[n][aim]; } }; /* - 时间复杂度:O(n * (sum + target)) - 空间复杂度:O(n * (sum + target)) */ // 空间优化版本 // 1、删除横坐标 // 2、修改纵坐标的遍历顺序 class Solution { public: int findTargetSumWays(vector<int>& nums, int target) { // 0、预处理 int n = nums.size(), sum = 0; for(const auto e : nums) sum += e; int aim = (sum + target) / 2; if(aim < 0 || (sum + target) % 2) return 0; // 1、建表 vector<int> dp(aim + 1); // 2、初始化 dp[0] = 1; // 3、填表 for(int i = 1; i <= n; ++i) for(int j = aim; j >= nums[i - 1]; --j) dp[j] += dp[j - nums[i - 1]]; // 4、返回值 return dp[aim]; } }; /* - 时间复杂度:O(n * (sum + target)) - 空间复杂度:O(sum + target) */
代码编写
// 未优化版本 class Solution { public: int lastStoneWeightII(vector<int>& stones) { // 0、预处理 int sum = 0; for(const auto e : stones) sum += e; int n = stones.size(), half = sum / 2; // 1、建表 && 初始化 vector<vector<int>> dp(n + 1, vector<int>(half + 1)); // 2、填表 for(int i = 1; i <= n; ++i) for(int j = 0; j <= half; ++j) { dp[i][j] = dp[i - 1][j]; if(j >= stones[i - 1]) dp[i][j] = max(dp[i][j], dp[i - 1][j - stones[i - 1]] + stones[i - 1]); } // 3、返回值 return sum - 2 * dp[n][half]; } }; /* - 时间复杂度:O(n * sum) - 空间复杂度:O(n * sum) */ //------------------------------------------------ // 空间优化版本 // - 删除 dp 表中的横坐标 // - 修改纵坐标遍历顺序 class Solution { public: int lastStoneWeightII(vector<int>& stones) { // 0、预处理 int sum = 0; for(const auto e : stones) sum += e; int n = stones.size(), half = sum / 2; // 1、建表 && 初始化 vector<int> dp(half + 1); // 2、填表 for(int i = 1; i <= n; ++i) for(int j = half; j >= stones[i - 1]; --j) dp[j] = max(dp[j], dp[j - stones[i - 1]] + stones[i - 1]); // 3、返回值 return sum - 2 * dp[half]; } }; /* - 时间复杂度:O(n * sum) - 空间复杂度:O(sum) */
算法原理(第一问)
算法原理(第二问)
优化
代码编写
#include <vector> #include <iostream> #include <algorithm> using namespace std; int main() { // 1、初始化 int n = 0, V = 0; cin >> n >> V; // n 个物品的体积和价值表,其中第 0 个位置仅仅用来占位 vector<int> v(n + 1), w(n + 1); for(int i = 1; i <= n; ++i) cin >> v[i] >> w[i]; // 2、建表 vector< vector<int> > dp(n + 1, vector<int>(V + 1)); // 解决第一问 for(int i = 1; i <= n; ++i) for(int j = 0; j <= V; ++j) { dp[i][j] = dp[i-1][j]; if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i][j-v[i]] + w[i]); } cout << dp[n][V] << endl; // 解决第二问 for(int j = 1; j <= V; ++j) dp[0][j] = -1; for(int i = 1; i <= n; ++i) for(int j = 0; j <= V; ++j) { dp[i][j] = dp[i-1][j]; if(j >= v[i] && dp[i][j-v[i]] != -1) dp[i][j] = max(dp[i][j], dp[i][j-v[i]] + w[i]); } cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl; return 0; } /* - 时间复杂度:O(nV) - 空间复杂度:O(nV) */ //--------------------------------------------------------------- // 空间优化版本 // 1. 删除横坐标 // 2. 纵坐标从左往右遍历 int main() { // 1、初始化 int n = 0, V = 0; cin >> n >> V; // n 个物品的体积和价值表,其中第 0 个位置仅仅用来占位 vector<int> v(n + 1), w(n + 1); for(int i = 1; i <= n; ++i) cin >> v[i] >> w[i]; // 2、建表 vector<int> dp(V + 1); // 解决第一问 for(int i = 1; i <= n; ++i) for(int j = v[i]; j <= V; ++j) dp[j] = max(dp[j], dp[j-v[i]] + w[i]); cout << dp[V] << endl; //解决第二问 for(int j = 1; j <= V; ++j) dp[j] = -1; // 继续优化:dp[j] = -0x3f3f3f3f for(int i = 1; i <= n; ++i) for(int j = v[i]; j <= V; ++j) // 或者如果用一个非常小的数(例如 -0x3f3f3f3f)代表背包装不满的情况,那么下面的语句就可以不用判断了 // 直接 dp[j] = max(dp[j], dp[j-v[i]] + w[i]); if(dp[j-v[i]] != -1) dp[j] = max(dp[j], dp[j-v[i]] + w[i]); cout << (dp[V] == -1 ? 0 : dp[V]) << endl;// 对于上面,修改为 cout << (dp[V] < 0 ? 0 : dp[V]) << endl; return 0; } /* - 时间复杂度:O(nV) - 空间复杂度:O(V) */
算法原理
代码编写
// 未优化版本 class Solution { public: const int INF = 0x3f3f3f3f; int coinChange(vector<int>& coins, int amount) { // 1、建表 int n = coins.size(); vector<vector<int>> dp(n + 1, vector<int>(amount + 1)); // 2、初始化 for(int j = 1; j <= amount; ++j) dp[0][j] = INF; // 3、填表 for(int i = 1; i <= n; ++i) for(int j = 0; j <= amount; ++j) { dp[i][j] = dp[i - 1][j]; if(j >= coins[i-1] && dp[i][j - coins[i-1]] != INF) dp[i][j] = min(dp[i][j], dp[i][j - coins[i-1]] + 1); } // 4、返回值 return dp[n][amount] == INF ? -1 : dp[n][amount]; } }; /* - 时间复杂度:O(n * amount) - 空间复杂度:O(n * amount) */ // ----------------------------------------------------------- // 优化版本 // 1、删除 dp 中的横坐标 // 2、纵坐标从左往右的顺序遍历 class Solution { public: const int INF = 0x3f3f3f3f; int coinChange(vector<int>& coins, int amount) { // 1、建表 int n = coins.size(); vector<int> dp(amount + 1, INF); // 2、初始化 dp[0] = 0; // 3、填表 for(int i = 1; i <= n; ++i) for(int j = coins[i-1]; j <= amount; ++j) dp[j] = min(dp[j], dp[j - coins[i-1]] + 1); // 4、返回值 return dp[amount] == INF ? -1 : dp[amount]; } }; /* - 时间复杂度:O(n * amount) - 空间复杂度:O(amount) */
算法原理
代码编写
// 未优化版本 class Solution { public: int change(int amount, vector<int>& coins) { // 1、建表 int n = coins.size(); vector< vector<int> > dp(n + 1, vector<int>(amount + 1)); // 2、初始化 dp[0][0] = 1; // 3、填表 for(int i = 1; i <= n; ++i) for(int j = 0; j <= amount; ++j) { dp[i][j] = dp[i - 1][j]; if(j >= coins[i - 1]) dp[i][j] += dp[i][j - coins[i - 1]]; } // 4、返回值 return dp[n][amount]; } }; /* - 时间复杂度:O(n * amount) - 空间复杂度:O(n * amount) */ // ----------------------------------------------------------------------------- // 利用滚动数组进行空间优化 // 1、删除 dp 表的横坐标 // 2、纵坐标从左往右遍历 class Solution { public: int change(int amount, vector<int>& coins) { // 1、建表 int n = coins.size(); vector<int> dp(amount + 1); // 2、初始化 dp[0] = 1; // 3、填表 for(const auto e : coins) for(int j = e; j <= amount; ++j) dp[j] += dp[j - e]; // 4、返回值 return dp[amount]; } }; /* - 时间复杂度:O(n * amount) - 空间复杂度:O(amount) */
题目解析
算法原理
代码编写
// 未优化版本 class Solution { public: int findMaxForm(vector<string>& strs, int m, int n) { // 1、建表 && 初始化 int len = strs.size(); vector<vector<vector<int>>> dp(len + 1, vector<vector<int>>(m + 1, vector<int>(n + 1))); // 2、填表 for(int i = 1; i <= len; ++i) { // 计算 strs[i] 中,'0' 和 '1' 的个数 int a = 0, b = 0; for(const auto e : strs[i - 1]) { if(e == '0') ++a; else ++b; } // 继续遍历其它维度 for(int j = 0; j <= m; ++j) for(int k = 0; k <= n; ++k) { dp[i][j][k] = dp[i-1][j][k]; if(j >= a && k >= b) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-a][k-b] + 1); } } // 3、返回值 return dp[len][m][n]; } }; /* - 时间复杂度:O(len * m * n) - 空间复杂度:O(len * m * n) */ // ---------------------------- // 利用滚动数组进行空间优化 // 1、删除 dp 表的横坐标 // 2、修改 j 和 k 维度的遍历顺序(改为从大到小) class Solution { public: int findMaxForm(vector<string>& strs, int m, int n) { // 1、建表 && 初始化 int len = strs.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); // 2、填表 for(int i = 0; i < len; ++i) { // 计算 strs[i] 中,'0' 和 '1' 的个数 int a = 0, b = 0; for(const auto e : strs[i]) { if(e == '0') ++a; else ++b; } // 继续遍历其它维度 for(int j = m; j >= a; --j) for(int k = n; k >= b; --k) dp[j][k] = max(dp[j][k], dp[j-a][k-b] + 1); } // 3、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(len * m * n) - 空间复杂度:O(len * m * n) */ // ---------------------------- // 利用滚动数组进行空间优化 // 1、删除 dp 表的横坐标 // 2、修改 j 和 k 维度的遍历顺序(改为从大到小) class Solution { public: int findMaxForm(vector<string>& strs, int m, int n) { // 1、建表 && 初始化 int len = strs.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); // 2、填表 for(int i = 0; i < len; ++i) { // 计算 strs[i] 中,'0' 和 '1' 的个数 int a = 0, b = 0; for(const auto e : strs[i]) { if(e == '0') ++a; else ++b; } // 继续遍历其它维度 for(int j = m; j >= a; --j) for(int k = n; k >= b; --k) dp[j][k] = max(dp[j][k], dp[j-a][k-b] + 1); } // 3、返回值 return dp[m][n]; } }; /* - 时间复杂度:O(len * m * n) - 空间复杂度:O(m * n) */
题目解析
算法原理
代码编写
// 未优化版本 class Solution { public: int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) { // 1、建表 int len = group.size(); vector<vector<vector<int>>> dp(len + 1, vector<vector<int>>(n + 1, vector<int>(minProfit + 1))); // 2、初始化 for(int j = 0; j <= n; ++j) dp[0][j][0] = 1; // 3、填表 for(int i = 1; i <= len; ++i) for(int j = 0; j <= n; ++j) for(int k = 0; k <= minProfit; ++k) { dp[i][j][k] = dp[i - 1][j][k]; if(j >= group[i - 1]) dp[i][j][k] += dp[i - 1][j - group[i - 1]][max(0, k - profit[i - 1])]; dp[i][j][k] %= (int)(1e9 + 7); } // 4、返回值 return dp[len][n][minProfit]; } }; /* - 时间复杂度:O(len * n * profit) - 空间复杂度:O(len * n * profit) */ // --------------------- // 利用滚动数组进行空间优化 // 1、删除 dp 表的横坐标 // 2、修改 j 和 k 维度的遍历顺序(改为从大到小) class Solution { public: int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) { int len = group.size(); // 1、建表 vector<vector<int>> dp(n + 1, vector<int>(minProfit + 1)); for(int j = 0; j <= n; ++j) dp[j][0] = 1; // 2、填表 for(int i = 0; i < len; ++i) for(int j = n; j >= group[i]; --j) for(int k = minProfit; k >= 0; --k) { dp[j][k] += dp[j - group[i]][max(0, k - profit[i])]; dp[j][k] %= (int)(1e9 + 7); } // 3、返回值 return dp[n][minProfit]; } }; /* - 时间复杂度:O(len * n * profit) - 空间复杂度:O(n * profit) */
题目解析
算法原理
代码编写
// 未优化版本 class Solution { public: int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) { // 1、建表 int len = group.size(); vector<vector<vector<int>>> dp(len + 1, vector<vector<int>>(n + 1, vector<int>(minProfit + 1))); // 2、初始化 for(int j = 0; j <= n; ++j) dp[0][j][0] = 1; // 3、填表 for(int i = 1; i <= len; ++i) for(int j = 0; j <= n; ++j) for(int k = 0; k <= minProfit; ++k) { dp[i][j][k] = dp[i - 1][j][k]; if(j >= group[i - 1]) dp[i][j][k] += dp[i - 1][j - group[i - 1]][max(0, k - profit[i - 1])]; dp[i][j][k] %= (int)(1e9 + 7); } // 4、返回值 return dp[len][n][minProfit]; } }; /* - 时间复杂度:O(len * n * profit) - 空间复杂度:O(len * n * profit) */ // --------------------- // 利用滚动数组进行空间优化 // 1、删除 dp 表的横坐标 // 2、修改 j 和 k 维度的遍历顺序(改为从大到小) class Solution { public: int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) { int len = group.size(); // 1、建表 vector<vector<int>> dp(n + 1, vector<int>(minProfit + 1)); for(int j = 0; j <= n; ++j) dp[j][0] = 1; // 2、填表 for(int i = 0; i < len; ++i) for(int j = n; j >= group[i]; --j) for(int k = minProfit; k >= 0; --k) { dp[j][k] += dp[j - group[i]][max(0, k - profit[i])]; dp[j][k] %= (int)(1e9 + 7); } // 3、返回值 return dp[n][minProfit]; } }; /* - 时间复杂度:O(len * n * profit) - 空间复杂度:O(n * profit) */
算法原理
代码编写
class Solution { public: int numTrees(int n) { // 1、建表 vector<int> dp(n + 1); // 2、初始化 dp[0] = 1; // 3、填表 for(int i = 1; i <= n; ++i) // i 代表节点数量 for(int j = 1; j <= i; ++j)// j 代表根节点 dp[i] += dp[j-1] * dp[i-j]; // 4、返回值 return dp[n]; } }; /* - 时间复杂度:O(n^2) - 空间复杂度:O(n^2) */
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。