312. 戳气球
记忆化搜索
因为模拟插破气球的过程要处理不相邻的数组元素,因此从无到有插入气球,先插入的相当于是后插破的
class Solution {
public:
vector<vector<int>> dp;
vector<int> val;
int solve(int l, int r) {
if (l >= r - 1) return 0; // 最少有俩气球才能在中间插入一个
if (dp[l][r] != -1) return dp[l][r];
for (int i = l + 1; i < r; i++) { // 枚举气球的插入位置
int sum = val[i] * val[l] * val[r];
sum += solve(l, i) + solve(i, r);
dp[l][r] = max(dp[l][r], sum); // 记录不同插入位置的最大值
}
return dp[l][r];
}
int maxCoins(vector<int>& nums) {
int n = nums.size();
val.resize(n + 2); // 再两端点插入值为1的气球,方便处理边界问题
dp.resize(n + 2, vector<int>(n + 2, -1));
for (int i = 1; i <= n; i++) val[i] = nums[i - 1];
val[0] = val[n + 1] = 1;
return solve(0, n + 1); // 返回在0到n+1之间插满气球的最大结果
}
};
区间dp
按照记忆化搜索的思路,可以进行自底向上推导
记dp[i][j]表示填满开区间(i, j)得到的最大硬币数
我们要保证计算时用的dp[i][k]是算过的,因此区间应该从小往大枚举
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
vector<int> val(n + 2, 1); // 再两端点插入值为1的气球,方便处理边界问题
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
for (int i = 1; i <= n; i++) val[i] = nums[i - 1];
for (int i = n - 1; i >= 0; i--) { // 枚举区间左端点
for (int j = i + 2; j <= n + 1; j++) { // 枚举右端点
for (int k = i + 1; k < j; k++) { // 枚举插入位置
int sum = val[i] * val[k] * val[j];
sum += dp[i][k] + dp[k][j];
dp[i][j] = max(sum, dp[i][j]);
}
}
}
return dp[0][n + 1]; // 返回在0到n+1之间插满气球的最大结果
}
};
如果想要第一层循环从小到大枚举,那么第一层循环应该是枚举右端点,因为要保证先计算小区间
class Solution {
public:
int maxCoins(vector<int>& nums) {
int n = nums.size();
vector<int> val(n + 2, 1);
vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
for (int i = 1; i <= n; i++) val[i] = nums[i - 1];
for (int j = 1; j <= n + 1; j++) { // 枚举右端点
for (int i = j - 2; i >= 0; i--) { // 枚举左端点
for (int k = i + 1; k < j; k++) { // 枚举插入位置
int sum = val[i] * val[k] * val[j];
sum += dp[i][k] + dp[k][j];
dp[i][j] = max(sum, dp[i][j]);
}
}
}
return dp[0][n + 1];
}
};