动态规划(一)最大最小值问题
本文主要是对动态规划(最大最小问题)的一个汇总
说明
类型说明
主要是根据一个target,查找抵达目标值的最小(大)成本/路径/总和
解题方法
在当前状态下之前的所以可能路径中选择最小(大)路径,然后再加上当前状态的值
routes[i] = min(routes[i-1], routes[i-2], ... , routes[i-k]) + cost[i]
为目标中的所有值生成最佳解决方案,并返回目标的值。
从上至下(记忆化搜索)
for (int j = 0; j < ways.size(); ++j) {
result = min(result, topDown(target - ways[j]) + cost/ path / sum);
}
return memo[/*state parameters*/] = result;
自底而上(动态规划)
for (int i = 1; i <= target; ++i) {
for (int j = 0; j < ways.size(); ++j) {
if (ways[j] <= i) {
dp[i] = min(dp[i], dp[i - ways[j]] + cost / path / sum) ;
}
}
}
return dp[target]
相关例题
例题汇总
编号 | 标题 | 难度 | 通过率 |
---|---|---|---|
64 | 最小路径和 | 中等 | 69.30% |
120 | 三角形最小路径和 | 中等 | 68.70% |
174 | 地下城游戏 | 困难 | 48.60% |
221 | 最大正方形 | 中等 | 49.10% |
279 | 完全平方数 | 中等 | 65.40% |
322 | 零钱兑换 | 中等 | 45.80% |
474 | 一和零 | 中等 | 46.80% |
570 | 只有两个键的键盘 | 中等 | 57.20% |
746 | 使用最小花费爬楼梯 | 简单 | 62.60% |
871 | 最低加油次数 | 困难 | 43.20% |
931 | 下降路径最小和 | 中等 | 67.10% |
983 | 最低票价 | 中等 | 63.40% |
1049 | 最后一块石头的重量 II | 中等 | 67.90% |
1240 | 铺瓷砖 | 困难 | 50.60% |
例题分析
最小路径和
// 最小路径求和
for(int i = 1; i < m; i++){
for(int j = 1; j < n; j++){
dp[i][j] = min(dp[i][j-1], dp[i-1][j]) + grid[i][j];
}
}
三角形最小路径和
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int m = triangle.size();
int n = triangle[m-1].size();
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[0][0] = triangle[0][0];
// 进行数组元素的初始化
for(int i = 1; i < m; i++){
dp[i][0] = triangle[i][0] + dp[i-1][0];
int j = triangle[i].size()-1;
dp[i][j] = triangle[i][j] + dp[i-1][j-1];
}
// 进行状态转移的计算
for(int i = 2; i < m; i++){
for(int j = 1; j < triangle[i].size()-1; j++){
dp[i][j] = min(dp[i-1][j] ,dp[i-1][j-1]) + triangle[i][j];
}
}
// 遍历最后一层的最小元素
int minimum = dp[m-1][0];
for(int i = 1; i < n; i++){
if(minimum > dp[m-1][i])
minimum = dp[m-1][i];
}
return minimum;
}
};
最大正方形
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> dp(m, vector<int>(n,0));
// 计算最大面积
int maxSide = 0;
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(matrix[i][j] == '1'){
if(i == 0 || j == 0){
dp[i][j] = 1;
}else
dp[i][j] = min(min(dp[i][j-1], dp[i-1][j]), dp[i-1][j-1]) + 1;
maxSide = max(maxSide, dp[i][j]);
}
}
}
return maxSide * maxSide;
}
};
完全平方数
解法几乎一样的题:零钱兑换|最后一块石头的重量 II
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
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);
}
}
return dp[n];
}
};
零钱兑换
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount+1, INT_MAX);
dp[0] = 0;
for(int i = 1; i <= amount; i++){
for(int j = 0; j < coins.size(); j++){
if(coins[j] <= i)
dp[i] = min(dp[i], dp[i-coins[j]] + 1);
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
};
最后一块石头的重量 II
class Solution {
public:
int lastStoneWeightII(vector<int> &stones){
int sum = accumulate(stones.begin(), stones.end(), 0);
int target = sum / 2;
vector<int> dp(target+1, 0);
for(int i = 1; i <= target; i++){
for(int j = 0; j < stones.size(); j++){
if(i >= stones[j])
dp[i] = max(dp[i], dp[i-stones[j]] + stones[j]);
}
}
return sum - 2 * dp[target];
}
};
一和零
class Solution {
public:
vector<int> getZerosOnes(string& str) {
vector<int> zerosOnes(2);
int length = str.length();
for (int i = 0; i < length; i++) {
zerosOnes[str[i] - '0']++;
}
return zerosOnes;
}
int findMaxForm(vector<string>& strs, int m, int n) {
int length = strs.size();
vector<vector<vector<int>>> dp(length + 1, vector<vector<int>>(m + 1, vector<int>(n + 1)));
// 进行状态转换计算
for (int i = 1; i <= length; i++) {
// 获取每个字符串的0和1的个数
vector<int>&& zerosOnes = getZerosOnes(strs[i - 1]);
int zeros = zerosOnes[0], ones = zerosOnes[1];
// 状态转换
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 >= zeros && k >= ones) {
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - zeros][k - ones] + 1);
}
}
}
}
return dp[length][m][n];
}
};
只有两个键的键盘
class Solution {
public:
int minSteps(int n) {
//dp[i] 表示打印出 i 个A 的最少操作次数,显而易见,dp[0]=0,dp[1]=0
vector<int> dp(n+1, 0);
for (int i = 2; i <= n; i++){
//每一个dp[i]最大值为i,即拷贝一次,粘贴i-1次
dp[i] = i;
for (int j = 2; j * j <= i; j++){
if (i % j == 0) {
//题解中:拷贝一次,粘贴 i/j-1次正好是dp[i/j],拷贝一次,粘贴j-1次,正好是dp[j],直接一个式子搞定
dp[i] = dp[j] + dp[i / j];
}
}
}
return dp[n];
}
};
下降路径最小和
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> dp(m, vector<int>(n));
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(i == 0){
dp[i][j] = matrix[i][j];
}else if(j == 0){
dp[i][j] = min(dp[i-1][j], dp[i-1][j+1]) + matrix[i][j];
}else if(j == n - 1){
dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + matrix[i][j];
}else
dp[i][j] = min(min(dp[i-1][j], dp[i-1][j-1]), dp[i-1][j+1]) + matrix[i][j];
}
}
int minn = INT_MAX;
for(int j = 0; j < n; j++){
minn = min(minn, dp[m-1][j]);
}
return minn;
}
};
最低票价
class Solution {
unordered_set<int> dayset;
vector<int> costs;
int memo[366] = {0};
public:
int mincostTickets(vector<int>& days, vector<int>& costs) {
this->costs = costs;
for (int d: days) {
dayset.insert(d);
}
memset(memo, -1, sizeof(memo));
return dp(1);
}
int dp(int i) {
if (i > 365) {
return 0;
}
if (memo[i] != -1) {
return memo[i];
}
if (dayset.count(i)) {
memo[i] = min(min(dp(i + 1) + costs[0], dp(i + 7) + costs[1]), dp(i + 30) + costs[2]);
} else {
memo[i] = dp(i + 1);
}
return memo[i];
}
};
参考文章
- Dynamic Programming Patterns:https://leetcode.com/discuss/general-discussion/458695/dynamic-programming-patterns
- 一篇文章吃透背包问题!(细致引入+解题模板+例题分析+代码呈现):https://app.yinxiang.com/fx/aa20d3b9-dc5e-4d40-8f27-e81c1928f772