3n块披萨
给你一个披萨,它由 3n 块不同大小的部分组成,现在你和你的朋友们需要按照如下规则来分披萨
每选一块,周围两块会被其他人分掉,请你返回你可以获得的披萨大小总和的最大值。
1. 动态规划
容易证明,可以选出任意一个长度为n的不相邻的数字序列
dp[i][j]表示前i个元素中,选择不相邻的j个元素最大值
class Solution {
public:
int maxSizeSlices(vector<int>& slices) {
int n = slices.size(); int k = n/3;
int dp[n][k+1];//动态规划,dp[i][j]表示前i个元素中,选择不相邻的j个元素最大值
function<int(int)> f = [&](int flag)->int{
memset(dp,0,sizeof(dp));
dp[0][1] = slices[0+flag];
dp[1][1] = max(slices[0+flag],slices[1+flag]);//必须初始化,避免越界
for(int i=2;i<n-1;i++)//遍历n-1个数,已经初始化两个
for(int j=1;j<=(i+2)/2&&j<=k;j++)//遍历已选个数范围,保证无后效性
dp[i][j] = max(dp[i-1][j],dp[i-2][j-1]+slices[i+flag]);//选与不选
return dp[n-2][k];
};
return max(f(0), f(1));
}
};
2. 反悔贪心
贪心选择最大的元素,同时将周围两个数合并掉(因为不能选)
合并后的数为反悔的契机,值为临近两数和减去当前选取的元素,在之后的过程中,选取该数,即意味着反悔,同时不影响后续的选取
这里为了简洁,不使用优先队列维护最大值,因为比较麻烦
优先队列在取出最大值后,需要得到最大值下标,以及其左右的元素,进行合并
class Solution {
public:
int maxSizeSlices(vector<int>& slices) {
int n = slices.size();
int need = n / 3;
int ret = 0;
for (int i = 0; i < need; i++) {
int max_pos = max_element(slices.begin(), slices.end()) - slices.begin();
int max_left = (max_pos + slices.size() - 1) % slices.size();
int max_right = (max_pos + slices.size() + 1) % slices.size();
int dis = slices[max_left] + slices[max_right] - slices[max_pos];
ret += slices[max_pos];
slices[max_pos] = dis;
slices.erase(slices.begin() + max(max_left, max_right));
slices.erase(slices.begin() + min(max_left, max_right));
}
return ret;
}
};
3. 反悔贪心 + 优先队列
class Solution {
public:
int maxSizeSlices(vector<int>& slices) {
int n = slices.size(); int k = n/3;
int res = 0;
vector<vector<int>> nums(n,vector<int>(4)); //每个vector代表一个节点,值分别为权重,左节点索引,右节点索引,自身索引
priority_queue<vector<int>> pq;//其实这里只需要权值和位置,为了统一节点,全部弄上去
for(int i=0;i<n;i++){
nums[i][0] = slices[i];
nums[i][1] = (i-1+n)%n; //左指针
nums[i][2] = (i+1+n)%n; //右指针
nums[i][3] = i;//记录一下位置
pq.push(nums[i]);
}
unordered_set<int> s;//记录被合并掉的位置
int i = 0;
while(i<k){//贪心选k个最大值
vector<int> cur = pq.top(); pq.pop(); //不能用这里面左右节点的索引,因为可能更新过
int pos = cur[3];//最大节点位置
if(s.count(pos)) continue;//跳过已被合并节点
vector<int> &left = nums[nums[pos][1]]; //左边节点
vector<int> &right = nums[nums[pos][2]]; //右边节点
//改变值
res = res + cur[0];//选取
i++;//计数增加
nums[pos][0] = left[0] + right[0] - cur[0]; //原位置更新新的反悔节点
//改变索引
nums[left[1]][2] = pos;
nums[right[2]][1] = pos;
nums[pos][1] = left[1];
nums[pos][2] = right[2];
//节点放回优先队列
pq.push(nums[pos]);
//剔除相邻节点
s.insert(left[3]);
s.insert(right[3]);
}
return res;
}
};