一、题目
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。
这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [3,1,5,8]
输出:167
解释:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
示例 2:
输入:nums = [1,5]
输出:10
提示:
n == nums.length
1 <= n <= 300
0 <= nums[i] <= 100
二、思路
一开始就确定了是动态规划,但找不到切入的角度。想了二十分钟,怎么将这个问题拆为小问题。
猜测在中间取小,最后戳两边,尝试后才发现被自己做成贪心了。
于是看了题解,大多是用三重for循环,外两层控制区间,里一层枚举最后戳哪个
即从后往前推,例如:
先取区间(0,2),中间只有nums[1],所以只能最后戳nums[1],计算得出结果后max取大存入dp[0][2]。
然后再取区间(0,3),中间有nums[1]和nums[2],假设最后戳nums[1],则结果为dp[0][1]+dp[1][3]+nums[0]*nums[1]*nums[2](前两者一者没有此情况,一者在上次循环就已经被计算);假设最后戳nums[2],则结果为dp[0][2]+dp[2][3]+nums[0]*nums[2]*nums[3](同理)。
这样就能逐步扩大区间,并在每个区间找到最大值。
三、尝试
通过的测试用例:12 / 73
完全错误的思路,属于是猜规律了。在情况不断变化的情况下,肯定是动态规划,而非贪心
cpp
class Solution {
private:
int _min;
int sum=0;
public:
int dp(vector<int>& nums,int n){
if(n<=2){
if(n==0) return 0;
else if(n==1) sum+=1*nums[0]*1;
else if(n==2) sum+=1*nums[0]*nums[1]+1*nums[1]*1;
return sum;
}
else{
_min=1;
for(int i=2;i<n-1;i++){
if(nums[_min]<=nums[i]) continue;
else _min=i;
}
sum+=nums[_min-1]*nums[_min]*nums[_min+1];
nums.erase(nums.begin()+_min);
return dp(nums,n-1);
}
}
int maxCoins(vector<int>& nums) {
return dp(nums,nums.size());
}
};
四、题解
cpp
class Solution {
private:
vector<vector<int>> dp;
public:
int maxCoins(vector<int>& nums) {
nums.insert(nums.begin(),1);
nums.push_back(1);
dp=vector<vector<int>>(nums.size(),vector<int>(nums.size(),0));
for(int left=nums.size()-2;left>=0;left--){
for(int right=left+1;right<=nums.size()-1;right++){
for(int last=left+1;last<right;last++){
dp[left][right]=max(dp[left][last]+dp[last][right]+nums[left]*nums[last]*nums[right],dp[left][right]);
}
}
}
return dp[0][nums.size()-1];
}
};