法一: 动态规划 O(n^2)
买了下标 i 的水果, 可以免费获得 (i+1) 个水果
dp[i][0] 表示 获得 0...i 的水果需要的 最少金币(i是免费获得的)
dp[i][1] 表示 获得 0...i 的水果需要的 最少金币(i是购买获得的)
dp[i][1] = min(dp[i-1][0],dp[i-1][1]) + prices[i]
dp[i][0] = min(dp[j][1]) j=i/2...i-1 // dp[i][0] : i要是想免费获得,必须是之前买了一个水果
class Solution {
public:
int minimumCoins(vector<int>& prices) {
int n=prices.size();
int dp[n][2];
dp[0][0]=INT_MAX/2; dp[0][1]=prices[0];
for(int i=1;i<n;i++){
dp[i][0]=dp[i][1]=INT_MAX;
for(int j=i/2;j<i;j++){
dp[i][0]=min(dp[i][0],dp[j][1]);
}//i=2t j=t i=2t+1 j=t
dp[i][1]=min(dp[i-1][0],dp[i-1][1])+prices[i];
}
return min(dp[n-1][0],dp[n-1][1]); //针对只有一个的 不然最小的铁定是 dp[n-1][0]
}
};
法二: 动态规划 O(n^2)
我们用一个dp[i]表示 当前买完 i...len-1 的水果需要的总金币
dp[i] = prices[i] + min dp[j] i+1 <= j <= 2*i+2 i < (len-1)/2
dp[i] = prices[i] i >= (len-1)/2
//记忆化搜索版本
class Solution {
public:
int minimumCoins(vector<int>& prices) {
//定义dfs[i]表示购买第i个,以及后面所有的水果需要的总花费
//买了第i个,那么可以免费获得第i+1,i+2...2i个水果
//下一个需要购买的就是 i+1,i+2,...2i+1,取这些情况下的最小值
int len=prices.size();
int memo[len]; memset(memo,-1,sizeof(memo));
function<int(int)> dfs=[&](int i)->int{
if(i>=(prices.size()+1)/2) return prices[i-1];
int &res=memo[i];
if(res!=-1) return res;
int price=INT_MAX;
for(int j=i+1;j<=2*i+1;j++){
price=min(price,dfs(j));
}
return res=price+prices[i-1];
};
return dfs(1);
}
};
//dp版本
class Solution {
public:
int minimumCoins(vector<int>& prices) {
//转换成dp,这里我们是把 dp[0...len-1] 对应 prices[0...len-1]
//买了下标i 可以免费获得i+1...i+i+1(一共i+1个)
//dp[i] 可以从 dp[i+1]...dp[i+i+1],dp[i+i+2]这些转移过来
int len=prices.size();
int dp[len];
for(int i=len-1;i>=0;i--){
if(i>=(len-1)/2){
dp[i]=prices[i]; continue;
}
dp[i]=INT_MAX/2;
for(int j=i+1;j<len && j<=2*i+2;j++)
dp[i]=min(dp[i],dp[j]);
dp[i]+=prices[i];
}
return dp[0];
}
};
法三: 单调队列 O(n)
买了下标i可以免费获得i+1...i+i+1(一共i+1个),也就是说dp[i]可以从 dp[i+1]...dp[i+i+1],dp[i+i+2]这些转移过来 也就是min{dp[j]} j=i+1...2i+2
注意到随着 i 的变小, j 的左右边界也在变小,所以可以使用类似滑动窗口的方法求最值,我们使用单调队列, 单调栈或者单调队列一般是按照for的遍历顺序拿到当前元素的,也就是说比如对于单调队列来说
我们的队头到队尾的序列是0,1,2...len-1的子序列或者是len-1,len-2,...0的子序列,明白了这一点,对于我们选择pop或者push的位置很重要
class Solution {
public:
int minimumCoins(vector<int>& prices) {
deque<int> dq;
int len=prices.size();
int dp[len];
for(int i=len-1;i>=0;i--){
while(dq.size() && dq.front()>2*i+2){//移除那些不满足限制的元素 如果front等于 2i+2
dq.pop_front();
}
if(i>=(len-1)/2){
dp[i]=prices[i];
}else{
dp[i]=prices[i]+dp[dq.front()];
}
while(dq.size() && dp[i]<=dp[dq.back()]){//维护当前的最小值 注意这里是要找当前i对应的窗口的最小值dp[j] 所以要用dp而不是prices
dq.pop_back();
}
dq.push_back(i);//这里添加到后面就是队头下标大 队尾下标小
}
return dp[0];
}
};