首先,我先解释一下候选的最大值的结点的队列是怎么一回事。
如果数组nums中的元素为223211,窗口为3,那么初始窗口框住的元素为2、2、3,此时3为最大值。
那我们如何对这个候选的最大值的结点的队列进行初始化呢?我们可以把2、2、3依次入队,并规定一条规则:如果新入队的值比原先队列中队尾的值都大,则将这些值弹出后再入队。
那为什么要定义这条原则呢?这是因为在初始化的时候,如果新入队的值比原先所有值都大,那这个新入队的值就是暂时的最大值,前面的值已经不再有候补意义。就像这里的2、2、3,等到3入队,那么窗口可能的最大值只会大于或等于3(而因为窗口大小为3,初始化到这里已经结束,不会再有新的值入队,所以最大值等于3),前面的2、2不可能成为初始化窗口的最大值了,所以直接弹出。
再如果数组nums中的元素为323211,则初始窗口框住的元素为3、2、3,在队列中入队出队的过程为:3入队,2入队,3准备入队,发现2小于3,则2出队,由于第三个3等于第一个3,所以第三个3入队,即初始化后队列中的值为3、3
现在,我们再来简单说一下执行中的过程。在执行的过程中,窗口以一次一个元素的速度进行移动,所以要用for循环对从第k+1个元素(即下标为k的元素)到nums数组最后一个元素进行遍历。在遍历中队列的变化与初始化的规则相似,即如果新入队的值比原先队列中队尾的值都大,则将这些值弹出后再入队。但由于窗口是在移动的,即使是世界上最强的人,也会面临生老病死的一天(最大的值也会随着窗口移动终有一日移出窗口),所以需要增加一个对最大值是否已经移出窗口的判断。如何判断呢?我们结构体中的下标排上了用场,如果某个值对应的结点的下标距离新入队的元素的距离为一个窗口的大小,说明已经移动出窗口了,那就要将其出队。当然,下标是需要赋值的,在对结点的值进行赋值的时候顺便赋了就行。
好了,主要部分应该大概可能也许已经解释清楚了,那思路就很明确了:
第一步:初始化队列
第二步:窗口移动,处理队列,并将每轮的最大值加入number数组
第三步:返回数组和大小
struct Node{
int key;//值
int index;//下标
};//自定义的结点,存放候选的最大值的值和下标
int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize){
int *number=malloc((numsSize)*sizeof(int)); //即存放窗口每一轮移动的最大值
struct Node queue[numsSize];//用于存放当前窗口内候选的最大值的结点的队列
int left=0;
int right=0;
int ind=0;//number数组的下标
queue[right].key=nums[0];//把第一个值先放入队列,方便写下面的循环体
queue[right++].index=0;
//这里把值和下标放入队列后在自增,即right指向队列最后一个元素的下一个位置
for(int i=1;i<k;i++){
while(right>left&&nums[i]>queue[right-1].key){
right--;
}
queue[right].key=nums[i];
queue[right++].index=i;
}
number[ind++]=queue[0].key;
//上面为第一步:初始化队列,并将初始队列的最大值放入number数组
for(int i=k;i<numsSize;i++){
while(right>left&&nums[i]>queue[right-1].key){
right--;
}
queue[right].key=nums[i];
queue[right++].index=i;
if(i-(queue[left].index)+1>k)//判断left指向的值是否已经移出窗口
left++;
number[ind++]=queue[left].key;
}
//上面为第二步:窗口移动,处理候选的最大值的队列
//并在每轮的最后将当轮的最大值加入number数组
*returnSize=ind;
return number;
//第三步:返回数组和大小
}