问题描述
小U手上有一个整数数组,他想知道如果从数组中删除任意一个元素后,能得到的长度为 k 的子数组和的最大值。你能帮小U计算出这个结果吗?
如果数组恰好为 k 个元素,那么不进行删除操作。
测试样例
样例1:
输入:
n = 5,k = 3,nums = [2, 1, 3, -1, 4]
输出:8
样例2:
输入:
n = 6,k = 2,nums = [-1, -1, 5, -2, 3, 4]
输出:8
样例3:
输入:
n = 4,k = 2,nums = [-5, -3, 2, 1]
输出:3
解题思路
本题可以转换为维护一个大小为k+1的窗口,维护窗口内的最小值。子数组和可以通过前缀和快速求出,答案就是这段的子数组和减去窗口内的最小值。遍历数组,求出最大结果即可。
单调队列详解
队列内的元素值是单调的,递增或递减。
求滑动窗口的最小值: 用单调队列储存当前窗口内递增的元素,并且队头是窗口内的最小值,队尾是窗口内的尾元素, 队列从队头到队尾对应窗口内从最小值到尾元素的一个子序列。
求滑动窗口的最大值: 用单调队列储存当前窗口内递减的元素,并且队头是窗口内的最大值,队尾是窗口内的尾元素, 队列从队头到队尾对应窗口内从最大值到尾元素的一个子序列。
算法流程
下面以求滑动窗口的最大值为例:
1.队头出队
当队头元素从窗口滑出时,队头元素出队(hh++)
2.队尾入队
当新元素滑入窗口时,要把新元素从队尾插入,分两种情况:
①直接插入
如果新元素小于队尾元素,直接从队尾插入(++tt),因为他可能在前面的最大值滑出窗口后成为最大值
②先删后插
如果新元素大于等于队尾元素,那就删除队尾元素(因为他不可能成为窗口中的最大值),删除方法是tt--,即从队尾出队,循环删除,直到队空或者遇到一个大于新元素的值,插入其后(++tt)
这样做每次都能从队头取得窗口中的最大值。
图解分析过程
如上图所示
给定数组2 7 9 8 5 5 1,窗口大小k为3
- 当i=1时,队空,2入队
- 当i=2时,当前元素7大于队尾元素2,2出队,7入队
- 当i=3时,当前元素9大于队尾元素7,7出队,9入队,此时i==k,窗口内最大值为9
- 当i=4时,当前元素8小于队尾元素9,8入队,此时i>k,窗口内最大值为9
- 当i=5时,当前元素5小于队尾元素8,5入队,此时i>k,窗口内最大值为9
- 当i=6时,队头元素9从窗口滑出,9出队,当前元素5等于队尾元素5,队尾元素5出队;当前元素5小于队尾元素8,5入队,此时i>k,,窗口内最大值为8
- 当i=7时,队头元素8从窗口滑出,8出队,当前元素1小于队尾元素5,1入队,此时i>k,窗口内最大值为5
所以滑动窗口位于每个位置时,窗口中的最大值为9 9 9 8 5
int q[N]; // 用于存储单调队列的索引
int solution(int n, int k, const std::vector<int>& nums) {
// 如果数组长度等于k,直接返回数组元素的和
if(n==k)return accumulate(nums.begin(),nums.end(),0);
vector<int>s(n+1); // 前缀和数组,s[i]表示前i个元素的和
// 计算前缀和数组,s[i]表示前i个元素的和
partial_sum(nums.begin(),nums.end(),s.begin()+1);
int ans=-1e9; // 初始化最大和为负无穷
int hh=0,tt=-1; // 初始化单调队列的头尾指针
for(int i=0;i<n;i++){
// 如果队列不为空且队列头部的索引小于当前窗口的最小索引,弹出队列头部
if(hh<=tt&&q[hh]<i-(k+1)+1)hh++;
// 维护单调队列,确保队列中的元素是递增的
while(hh<=tt&&nums[q[tt]]>=nums[i])tt--;
q[++tt]=i; // 将当前索引加入队列
if(i>=k){
// 计算当前窗口的最大和,并更新ans
ans=max(ans,s[i+1]-s[i+1-(k+1)]-nums[q[hh]]);
// cout<<nums[q[hh]]<<" ";
}
}
// cout<<ans<<'\n';
return ans; // 返回最大和
}
STL双向队列写法
//求窗口最大值
deque<int>q;
for(int i=0;i<n;i++)
{
if(!q.empty()&&q.front()<i-k+1)q.pop_front();
//判断队头是否需要出队
while(!q.empty()&&a[q.back()]<=a[i])q.pop_back();
//维护队列单调性
q.push_back(i);
//下标入队,便于队头出队
if(i>=k-1)cout<<a[q.front()]<<' ';
//取队头元素作为窗口最大元素
}
本题总结
本题的目标是找到在删除数组中的任意一个元素后,能够得到的长度为 k 的子数组和的最大值。我们需要通过以下步骤来实现:
1. 特殊情况处理
- 如果数组长度
n恰好等于k,那么不需要删除任何元素,直接返回数组的总和。
2. 数据结构选择
- 前缀和数组:用于快速计算任意子数组的和。
- 单调队列:用于维护当前窗口内的最小值索引,以便在删除一个元素后,能够快速找到新的最大子数组和。
3. 算法步骤
- 计算前缀和:首先计算数组的前缀和数组
s,其中s[i]表示nums数组前i个元素的和。 - 滑动窗口:使用一个滑动窗口来维护当前窗口内的最小值索引。
- 更新最大值:在滑动窗口的过程中,计算删除一个元素后的子数组和,并更新最大值。
4. 具体实现
- 初始化前缀和数组
s。 - 使用一个队列
q来维护当前窗口内的最小值索引。 - 遍历数组,更新队列
q,并计算删除一个元素后的子数组和,更新最大值。