2021-01-02 239 [Deque]

SuperFeHanHan發表於2021-01-02

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(nk+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] ij,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{ideque 使得LiR}

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]

相關文章