单调栈的应用
單調棧!! 從滑動窗口說起
- 單調棧的應用①
- 單調棧的應用②
滑動窗口最大值
此題是滑動窗口中的數字個數是固定的,如果窗口中的數字個數在動態變化,比如R右邊界增加,L不變…
那有沒有一種數據結構結構,可以讓我們以O(1)的復雜度得到此時滑動窗口中的最大值或者最小值呢???
有,那就是單調棧!!!
可以使用雙端隊列來是實現
該單調棧中保證從頭部到尾部保證數據的嚴格遞增或遞減;
(不允許存在相等的)
當R往右移動一個位置時,如果此時R位置的數,滿足單調中的嚴格遞增或遞減順序,則放入到隊列尾部
如果不滿足,則依次彈出隊列尾部的值,直到放入的數滿足嚴格遞增或遞減的順序
當L往右動時,如果L此時的位置,是雙端隊列頭部的值,則刪除頭部的值
單調棧為什么能以O(1)的復雜度得到滑動窗口中的最大值或者最小值呢???
因為它維護的是信息是:
當R不往右動時,L此時位置的數過期,該窗口中誰會成為最大值的順序;
比如 6 5 4 3 5 ,此時L下標為-1, R下標為3
此時隊列為
6 5 4 3
如果L往右動,那么滑動窗口中成為最大值為5
再往右動最大值為4
分析一下為什么?
當R往右動,即窗口此時又包含了5,此時單調棧中需要把3,4移除隊列
因為根據單調棧維護的信息,知道
我5比3,4都要大,而且我要晚過期,所以,只要有我5在,3,4就可能成為此時窗口的最大值,因此可以把它們移除
分析一下時間復雜度:
所有元素進出單調棧(雙端隊列一次),因此是O(n),每個元素評價下來就是O(n)/n相對于O(1)
以下是通用代碼,即窗口值不固定,當我們R往右移動時,調用addNumFromRight方法即可
當L往右移動時,調用WindoxMax中的RemovenumFromLeft
單調棧的應用①
如果給定一個數組,我們想知道每一個數,左邊比它大的且離它最近,右邊比它的且離它最近的數分別是什么???
常規方法是,遍歷這個數的左邊和右邊,可以得到結果,但是這樣時間復雜度是O(n2)
這題我們可以利用單調棧把整體復雜度降低為O(n)
先考慮數組中不出現重復值的情況
由于這里是要找左邊,右邊最近且最大的值,因此這里維護一個從底部到頂部從大到小的順序,如果滿足單調棧的遞減順序,就壓入棧,如果不滿足,則彈出棧頂元素,并且生成棧頂元素的信息,左邊離你最近且最大的就是該元素下面一個(這里是4),右邊最近最大的就是讓你彈出的那個元素(這里是6)
接著由于6比4大,繼續彈出,生成信息
6比5大,繼續彈出生成信息
…
如果到最后棧中還有元素,則進入清算階段
從棧頂開始彈出元素,右邊最近最大的沒有,左邊最近最大的就是該元素下面的元素;
這樣的時間復雜度是O(n)的,因為數組中元素只會進出單調棧一次
接下來證明:
假設此時數組中沒有重復值,數組時從左往右進入棧
此時要壓入c,c比a大,此時要彈出a并且生成a的信息
①為什么當a彈出時,數組右邊離a最近且最大的一定是c
假設如果數組中,a 和 c之間有比a大的數k,由于數組時從左往右遍歷的,當我們遍歷到k的時候,就會把a彈出了,并且生成a的信息,但是現在是輪到c來把a彈出,這就說明此時c已經是a右邊最近且最大的值了
②為什么當a彈出時,數組左邊離a最近且最大的一定是b
同樣假設如果原數組中中a和b之間有其他數,假設存在一個k
b … k …a
那么k有可能有以下3種情況:
所以: 可證明,當彈出a時,a左邊比a大的且最近的就是b,即單調棧維護的信息時正確的!!
代碼
這里代碼展示的是找一個數字,左邊小于它并且最近的數,右邊小于它并且最近的數,那么我們只需要保持單調棧從底部到頭部是遞增即可
此時來看看如果數組中有重復值
如果有重復值,那么只需要把相同值的下標放在一起,串成一個鏈表!!
比如: 此時來了一個6位置的5,此時要彈出5位置的3,那么右邊比3大的數且最近的 就是6位置的5
左邊比3大的就是相同值為5串成的鏈表的最后一個,也就是4位置的5
代碼:
public static int[][] getNearLess(int[] arr) {int[][] res = new int[arr.length][2];Stack<List<Integer>> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {List<Integer> popIs = stack.pop();// 取位于下面位置的列表中,最晚加入的那個int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);for (Integer popi : popIs) {res[popi][0] = leftLessIndex;res[popi][1] = i;}}if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) {stack.peek().add(Integer.valueOf(i));} else {ArrayList<Integer> list = new ArrayList<>();list.add(i);stack.push(list);}}while (!stack.isEmpty()) {List<Integer> popIs = stack.pop();// 取位于下面位置的列表中,最晚加入的那個int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);for (Integer popi : popIs) {res[popi][0] = leftLessIndex;res[popi][1] = -1;}}return res;}單調棧的應用②
一個數組中子數組的累計和與最小值的乘積叫作指標A,給定一個正數數組,
請返回這個數組中,A的最大值
算法部分看max2方法,max1方法是用的暴力法,用對數器來驗證max2是否是正確的!!!
總結
- 上一篇: C++弹窗拦截程序,弹窗广告怎么关闭?不
- 下一篇: 解决高度坍塌