本文字數:2000字
閱讀本文大概需要:5 分鐘
做算法題了,題的難度我們分爲“士,尉,校,將”四個等級。這個算法題的模塊是篇幅比較小的那種模塊。首先是給出一道題的描述,之後我會用我的想法來做這道題,今天算是算法題的第一道題,先來試試水。
問題描述(等級:尉)
有一個整型數組arr和一個大小爲w的窗口從數組的最左邊滑到最右邊,窗口每次向右邊滑一個位置。
例如,數組爲[4,3,1,5,4,3,7,5],窗口大小爲5時:
[4 3 1 5 4] 3 7 5 max = 5
4 [3 1 5 4 3] 7 5 max = 5
4 3 [1 5 4 3 7] 5 max = 7
4 3 1 [5 4 3 7 5] max = 7
即窗口最大值數組爲 result = {5, 5,7,7}
解答:
對於一道題,我一般會第一時間想到用暴力的方法來做,之後再來慢慢優化。
顯然,對於這道題用暴力法來做還是挺簡單了,窗口每次向右移動一位時,我們每次遍歷窗口內的w個元素,然後求出此時窗口的最大值就可以了,用這種方法的時間複雜度是 O(wn)。代碼如下:
//暴力法求解 public static int[] getMaxWindow(int[] arr, int w) { if (w < 1 || arr == null || arr.length < w) { return null; } int[] result = new int[arr.length - w + 1]; int index = 0; //暴力求解直接從第 w-1個元素開始遍歷 for (int i = w - 1; i < arr.length; i++) { int max = arr[i]; //找出最大值 for (int k = i; k > i - w; k--) { if (max < arr[k]) { max = arr[k]; } } result[index++] = max; } return result; }
注:可以左右拉動
大家想一個問題,例如對於剛纔例題中的數組:
第一次遍歷的時候,max = 5
第二次遍歷的時候,max = 5
我們剛纔用暴力法的時候,無論是第一次還是第二次,我們都是把窗口內的所有元素都給遍歷了一次,以此來尋找最大值,可是,真的需要這樣嗎?
第一次遍歷的時候,我們找出了max = 5, 那麼在第二次遍歷的時候,在窗口範圍內,max = 5 左邊的兩個數1, 3 還有可能是最大值嗎?也就是說,max=5 左邊的窗口元素還要必要遍歷嗎?
顯然,max=5左邊的窗口實際上是不必再遍歷的了,也就是它不可能會是窗口的最大值。
而 max = 5 右邊的 4 有可能會是窗口的最大值嗎?由於窗口還會一直向右移動,所以 max = 5 右邊的窗口元素還是有可能是某一個窗口的最大值的。
因此,我們可以用一個雙向的隊列,來記錄有可能成爲窗口最大值的下標,注意,這裏指的是有可能。
像剛纔的 max = 5 前面的 1,3 就不可能成爲窗口的最大值了,而右邊的4還是有可能成爲窗口的最大值的。並且這個隊列是有序的,隊首存放的總是隊列中的最大值,
我以這道題來演示一下,我們用result[] 數組來存放窗口最大值。
1、result[0] = 5
2、result[1] = 5;
3、result[2] = 7
其他的全部都要出隊,因爲7前面的5,4,3是不可能成爲窗口最大值的了。
4、result[3] = 7
遍歷完畢。這種方法的話時間複雜度是 O(n)。
我這裏只是提供了思路與大致的做法,具體的代碼實現還是有很多細節需要注意的。下面給出實現代碼,代碼會有詳細的解釋。
//優化 public static int[] getMaxWindow2(int[] arr, int w) { if (w < 1 || arr == null || arr.length < w) { return null; } //用來保存成爲最大窗口的元素 int[] result = new int[arr.length - w + 1]; int index = 0; //用鏈表從當雙向隊列。 LinkedList<Integer> temp = new LinkedList<>(); //剛纔演示的時候,我i直接從i = w-1那裏開始演示了。 for (int i = 0; i < arr.length; i++) { //如果隊列不爲空,並且存放在隊尾的元素小於等於當前元素,那麼 //隊列的這個元素就可以彈出了,因爲他不可能會是窗口最大值。 //【當前元素】指的是窗口向右移動的時候新加入的元素。 while (!temp.isEmpty() && arr[temp.peekLast()] <= arr[i]) { temp.pollLast();//把隊尾元素彈出 } //把【當前元素】的下邊加入到隊尾 temp.addLast(i); //如果隊首的元素不在窗口範圍內,則彈出 if (temp.peekFirst() == i - w) { temp.pollFirst();// } if (i >= w - 1) { //由於隊首存放的是最大值,所以隊首總是對應窗口的最大值元素 result[index++] = arr[temp.peekFirst()]; } } return result; }