2021-01-02 239 [Deque]
239. 滑動視窗最大值
思路一:(超時)
- 如果在最新的區間最右側值C,比上一個區間最左側的值A要大,則因為兩者共用區間中最大值B一定比A要小,所以如果C比A大,則新區間最大值一定是C。
- 否則,我們就從頭掃描這個區間得到最大值
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int l = nums.length;
int[] res = new int[l-k+1];
int tmp = nums[0]; //因為nums.length>=1
for(int i=1;i<k;i++) // k<=l
tmp = Math.max(tmp,nums[i]);
res[0]=tmp;
for(int i=1;i<l-k+1;i++){
// 如果最大值是在新的區間裡取到
if(nums[i+k-1]>=res[i-1])
res[i] = nums[i+k-1];
//否則,就是在找res[i,i+k-1]之中的最大值
else{
int tmp2 = nums[i];
for(int j=i+1;j<i+k;j++)
tmp2 = Math.max(tmp2,nums[j]);
res[i]=tmp2;
}
}
return res;
}
}
複雜度:
在最壞的情況下,即
A
>
C
A>C
A>C的情況下為
O
(
N
2
)
O(N^2)
O(N2
思路二:PriorityQueue(依舊超時)
在這裡我們可以引入一個單調佇列,每次保證它只有k個元素,這樣每次新增和刪除一個元素只要用 O ( l o g k ) O(logk) O(logk),一共進行 O ( n − k + 1 ) O(n-k+1) O(n−k+1)次操作,所以複雜度為 O ( n l o g k ) O(nlogk) O(nlogk)。
public int[] maxSlidingWindow(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>(k,new Comparator<Integer>() {
@Override
public int compare(Integer i1, Integer i2) {
return i2 - i1;
}
});
int l = nums.length;
int[] res = new int[l-k+1];
// 因為nums.length >= k
for(int i=0;i<k;i++)
pq.add(nums[i]);
res[0]=pq.peek();
for(int i=1;i<res.length;i++){
pq.remove(nums[i-1]);
pq.add(nums[i+k-1]);
res[i]=pq.peek();
}
return res;
}
思路三:用Deque實現的單調佇列
思路:
我們用一個雙向連結串列來儲存陣列中的下標,它滿足如下的性質:
- ∀ i ≤ j , n u m s [ i ] ≥ n u m s [ j ] \forall i\leq j, nums[i]\geq nums[j] ∀i≤j,nums[i]≥nums[j]
因此,我們每次找最大值的時候,對於當前視窗[L,R],只要找 m i n { i ∈ deque使得 L ≤ i ≤ R } min\{i \in \text{deque 使得} L \leq i\leq R\} min{i∈deque使得L≤i≤R}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
Deque<Integer> deque = new LinkedList<Integer>();
// 初始化第一個視窗
for (int i = 0; i < k; ++i) {
// 如[6,5,3,2] 加 4,則這個while會把原來的dequeue變成[6,5]
// 注意如果相等的話,我們也要取新的大一點的下標
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offerLast(i);
}
int[] ans = new int[n - k + 1];
ans[0] = nums[deque.peekFirst()];
for (int i = k; i < n; ++i) {
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offerLast(i);
// 刪除多餘的元素,這裡的R=i,L=i-k+1 (出while迴圈下標i一定>i-k)
while (deque.peekFirst() <= i - k) {
deque.pollFirst();
}
ans[i - k + 1] = nums[deque.peekFirst()];
}
return ans;
}
}
Deque
// 初始化
Deque<Integer> deque = new LinkedList<Integer>();
// 新增元素 O(1)
deque.offerFirst(1);
deque.offerLast(2);
// 檢視一個元素 O(1)
deque.peekFirst();
deque.peekLast();
// 刪除一個元素 O(1)
System.out.println(deque.pollFirst());
System.out.println(deque.pollLast());
例子:
- 輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
- 輸出: [3,3,5,5,6,7]
- 初始狀態:L=R=0,佇列:{},其中L,R為當前視窗的下標。
i=0,nums[0]=1,佇列為空,直接加入。佇列:{0}
i=1,nums[1]=3,隊尾值為nums[0]=1<3,所以彈出隊尾值。佇列:{1}
i=2,nums[2]=-1,隊尾值為nums[1]=3>-1,直接加入,佇列:{1,2} 此時視窗形成 L=0,R=2,result=nums[1]=3
i=3,nums[3]=-3,隊尾值為nums[2]=-1>-3,直接加入,佇列:{1,2,3} 此時L=1,R=3,nums[1]有效,result=[3,3]
i=4,nums[4]=5,隊尾值為nums[3]=-3<5,依次彈出後加入,佇列:{4} 此時L=2,R=4,nums[4]有效,result=[3,3,5]
i=5,nums[5]=3,隊尾值為nums[4]=5>3,直接加入,佇列:{4,5},此時L=3,R=5,nums[4]有效,result=[3,3,5,5]
i=6,nums[6]=6,隊尾值為nums[5]=3<6,依次彈出後加入。佇列:{6},此時L=4,R=6,nums[5]有效,result=[3,3,5,5,6]
i=7,nums[7]=7,隊尾值為nums[6]=6<7,依次彈出後加入。佇列:{7},此時L=5,R=7,nums[7]有效,result=[3,3,5,5,6,7]