虾皮一面问了redis、MySQL、Http,前面答得都很好,面试官评价也挺好的,然后面试官说做完这个算法题,如果能做出来,面试就过了。
面试题:滑动窗口最大值JZ59
我的思路:
- 通过双指针实现窗口(大小为k)在数组(大小为n)上的滑动,并用一个索引记录当前窗口最大值的位置,如果最大值被划出窗口了,就重新计算新窗口的最大值。
- 这样的时间复杂度是O(nk)
1 class Solution { 2 public: 3 // 记录滑动窗口的最大值=》减少向量比较次数 4 vector<int> maxInWindows(const vector<int>& num, unsigned int size) { 5 // 双指针 6 int vlen=num.size(); 7 vector<int> res; 8 if(vlen<size || size==0){ 9 return res; 10 } 11 // 记录最大值索引值更好点,key可以直接定位val,但val不能定位key 12 int lp=0,rp=size-1; 13 int maxIdx=maxValue(num,lp,rp); // 处理边界 14 lp++;rp++; 15 res.push_back(num[maxIdx]); 16 while(rp<vlen){ 17 if(num[rp]>num[maxIdx]){ // 先把rp元素拉入区间 18 maxIdx=rp; 19 } 20 if(maxIdx<lp){ // 如果最大值的索引值已经在区间左侧了 21 maxIdx=maxValue(num,lp,rp); 22 } 23 res.push_back(num[maxIdx]); 24 ++lp;++rp; 25 } 26 return res; 27 } 28 29 // 返回一个窗口的最大值的索引值 30 int maxValue(vector<int> wick,int lp,int rp){ 31 int maxIndex=lp; 32 for(int i=lp+1;i<=rp;++i){ 33 maxIndex=wick[i]>wick[maxIndex]?i:maxIndex; 34 } 35 return maxIndex; 36 } 37 };
然后,我做出来了,面试官从最开始的高评价变成了整体还不错,然后就收到感谢信了!!!!
面试官最后说这题的思路应该是用优先队列,看了下牛客,记录下用双端队列的解题思路吧。
双端队列的核心思想:
- 记录一个窗口起点为最大值,终点为当前值的递减序列,如果6 2 5 2 4 1 7,窗口大小为5,那么双端队列的第一个窗口存储值就是6 5 4;
当6被划出去后,5就是当前窗口最大值了。 因此,我们在将当前值插入队列时,需要从后到前删除比它小的值。
- 为了判断6什么时候会被划走,不是直接存储val 6 而是存储 key 0. 当 i-deque.front+1>k时,就表示上个窗口的最大值被移走了。
1 class Solution { 2 public: 3 vector<int> maxInWindows(const vector<int>& num, unsigned int size) { 4 vector<int> res; 5 deque<int> deq; 6 int len=num.size(); 7 // 这个条件判断很重要,面试官也是针对这个问题说了我下 8 if(num.empty() || size<=0 || size>len) return res; 9 for(int i=0;i<len;++i){ 10 while(!deq.empty() && num[deq.back()]<num[i]){deq.pop_back();} 11 while(!deq.empty() && i-deq.front()+1>size){deq.pop_front();} 12 // 将当前索引追加到双端队列尾巴 13 deq.push_back(i); 14 // 当窗口为size时,才写入结果 15 if(i+2>size){res.push_back(num[deq.front()]);} 16 } 17 return res; 18 } 19 };
这种方法时间复杂度是O(n),比如最坏的情况,整个数组是顺序的,执行时间好像也只是T(2n),第二个while用 if 替代也是可以的。