劍指Offer面試題65:滑動窗口的最大值 Java實現

題目:給定一個數組和滑動窗口的大小,請找出所有滑動窗口裏的最大值。如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那麼一共存在6個滑動窗口,他們的最大值分別爲{4.4,6,6,6,5}。
算法分析:
 
        如果採用蠻力法,這個問題似乎不難解決:可以掃描每一個滑動窗口的所有數字並找出其中的最大值。如果滑動窗口的大小爲k,需要O(k)時間才能找出滑動窗口裏的最大值。對於長度爲n的輸入數組,這個算法總的時間複雜度是O(nk)。
  實際上一個滑動窗口可以看成是一個隊列。當窗口滑動時,處於窗口的第一個數字被刪除,同時在窗口的末尾添加一個新的數字。這符合隊列的先進先出特性。如果能從隊列中找出它的最大數,這個問題也就解決了。
  在面試題21中。我們實現了一個可以用O(1)時間得到最小值的棧。同樣,也可以用O(1)時間得到棧的最大值。同時在面試題7中,我們討論瞭如何用兩個棧實現一個隊列。綜合這兩個問題的解決方法,我們發現如果把隊列用兩個棧實現,由於可以用O(1)時間得到棧中的最大值,那麼也就可以用O(1)時間得到隊列的最大值,因此總的時間複雜度也就降到了O(n)。
  我們可以用這個方法來解決問題。不過這樣就相當於在一輪面試的時間內要做兩個面試題,時間未必夠用。再來看看有沒有其它的方法。
  下面換一種思路。我們並不把滑動窗口的每個數值都存入隊列中,而只把有可能成爲滑動窗口最大值的數值存入到一個兩端開口的隊列。接着以輸入數字{2,3,4,2,6,2,5,1}爲例一步分析。
  數組的第一個數字是2,把它存入隊列中。第二個數字是3.由於它比前一個數字2大,因此2不可能成爲滑動窗口中的最大值。2先從隊列裏刪除,再把3存入到隊列中。此時隊列中只有一個數字3.針對第三個數字4的步驟類似,最終在隊列中只剩下一個數字4.此時滑動窗口中已經有3個數字,而它的最大值4位於隊列的頭部。
  接下來處理第四個數字2。2比隊列中的數字4小。當4滑出窗口之後2還是有可能成爲滑動窗口的最大值,因此把2存入隊列的尾部。現在隊列中有兩個數字4和2,其中最大值4仍然位於隊列的頭部。
  下一個數字是6.由於它比隊列中已有的數字4和2都大,因此這時4和2已經不可能成爲滑動窗口中的最大值。先把4和2從隊列中刪除,再把數字6存入隊列。這個時候最大值6仍然位於隊列的頭部。
  第六個數字是2.由於它比隊列中已有的數字6小,所以2也存入隊列的尾部。此時隊列中有兩個數字,其中最大值6位於隊列的頭部。
  接下來的數字是5.在隊列中已有的兩個數字6和2裏,2小於5,因此2不可能是一個滑動窗口的最大值,可以把它從隊列的尾部刪除。刪除數字2之後,再把數字5存入隊列。此時隊列裏剩下兩個數字6和5,其中位於隊列頭部的是最大值6.
  數組最後一個數字是1,把1存入隊列的尾部。注意到位於隊列頭部的數字6是數組的第5個數字,此時的滑動窗口已經不包括這個數字了,因此應該把數字6從隊列刪除。那麼怎麼知道滑動窗口是否包括一個數字?應該在隊列裏存入數字在數組裏的下標,而不是數值。當一個數字的下標與當前處理的數字的下標之差大於或者等於滑動窗口的大小時,這個數字已經從滑動窗口中滑出,可以從隊列中刪除了。

隊列:
隊列(簡稱作隊,Queue)也是一種特殊的線性表,隊列的數據元素以及數據元素間的邏輯關係和線性表完全相同,其差別是線性表允許在任意位置插入和刪除,而隊列只允許在其一端進行插入操作在其另一端進行刪除操作。 
隊列中允許進行插入操作的一端稱爲隊尾,允許進行刪除操作的一端稱爲隊頭。隊列的插入操作通常稱作入隊列,隊列的刪除操作通常稱作出隊列。
下圖是一個依次向隊列中插入數據元素a0,a1,...,an-1後的示意圖:
 
算法實現源程序:
/**************************************************************      
* Copyright (c) 2016, 
* All rights reserved.                   
* 版 本 號:v1.0                   
* 題目描述:給定一個數組和滑動窗口的大小,請找出所有滑動窗口裏的最大值。如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,
* 			那麼一共存在6個滑動窗口,他們的最大值分別爲{4.4,6,6,6,5}。
* 輸入描述:請輸入一個數組:以空格隔開
*			2 3 4 2 6 2 5 1
*			請輸入滑動窗口的大小:
*			3
* 程序輸出: 滑動窗口的最大值爲:
*			[4, 4, 6, 6, 6, 5]
* 問題分析: 隊列:隊列中允許進行插入操作的一端稱爲隊尾,允許進行刪除操作的一端稱爲隊頭。隊列的插入操作通常稱作入隊列,隊列的刪除操作通常稱作出隊列。
* 算法描述:見程序
* 完成日期:2016-10-18
***************************************************************/ 

package org.marsguo.offerproject65;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Scanner;

class SolutionMethod1{
	public ArrayList<Integer> maxInWindows(int[] num,int size){
		ArrayList<Integer> ret = new ArrayList<>();
		if(num == null){
			return ret;
		}
		if(num.length < size || size < 1){
			return ret;
		}
		
		LinkedList<Integer> indexDeque = new LinkedList<>();		//用於保存滑動窗口中的數字
		
		/*
		滑動窗口內部,用於判斷窗口中的最大值
		*/
		for(int i = 0; i < size - 1; i++){
			while(!indexDeque.isEmpty()&& num[i] > num[indexDeque.getLast()]){			//getLast爲插入端
				indexDeque.removeLast();			//將前面比K小的直接移除隊列,因爲不可能成爲滑動窗口的最大值
			}
			indexDeque.addLast(i);					//將數字存入滑動窗口中
		}
		 
		/*
		滑動整個窗口
		*/
		for(int i = size - 1; i < num.length; i++){
			while(!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]){			//getLast爲插入端,隊尾
				indexDeque.removeLast();				//將前面比K小的直接移除隊列,因爲不可能成爲滑動窗口的最大值
			}
			indexDeque.addLast(i);
			//System.out.println("indexDeque = " + indexDeque.getFirst() + ",i = " + i);				//getFirst爲允許刪除端,隊頭
			if(i - indexDeque.getFirst() + 1 > size){
				indexDeque.removeFirst();
			}
			
			ret.add(num[indexDeque.getFirst()]);		//每次添加的是num[indexDeque.getFirst()],而不是indexDeque.getFirst().
		}
		return ret;
	}
}

public class MaxInWindows {
	public static void main(String[] args){
		Scanner scanner = new Scanner(System.in);
		System.out.println("請輸入一個數組:以空格隔開");
		String str = scanner.nextLine();
		
		System.out.println("請輸入滑動窗口的大小:");
		int k = scanner.nextInt();
		
		String[] tmp = str.split(" ");
		int[] arrays = new int[tmp.length];
		for(int i = 0; i < tmp.length;i++){
			arrays[i] = Integer.parseInt(tmp[i]);
		}
		scanner.close();
		
		SolutionMethod1 solution1 = new SolutionMethod1();
		System.out.println("滑動窗口的最大值爲:");
		System.out.println(solution1.maxInWindows(arrays, k));
	}
}


程序運行結果:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章