日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

单调栈的应用

發布時間:2023/12/20 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 单调栈的应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

單調棧!! 從滑動窗口說起

    • 單調棧的應用①
    • 單調棧的應用②


滑動窗口最大值


此題是滑動窗口中的數字個數是固定的,如果窗口中的數字個數在動態變化,比如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種情況:

  • k > b : 這種情況不可能存在,因為如果k>b,那么k就已經把b給替換了
  • a < k < b : 這種情況也不可能,因為此時a,b不可能挨著一起,你也許回想,有可能來了一個比k大的數,然后把k給替換了,但是我們這里是無重復值的情況,因此你無論怎么替換最終不可能把b給替換了,即單調棧在a,b之間夾了一個數,因此這種情況不存在
  • k < a: 這種情況就允許存在了,因為如果原數組中b,a之間全是比a小的數,那么最終就會形成單調棧的情況 a在b之上;
    所以: 可證明,當彈出a時,a左邊比a大的且最近的就是b,即單調棧維護的信息時正確的!!
  • 代碼
    這里代碼展示的是找一個數字,左邊小于它并且最近的數,右邊小于它并且最近的數,那么我們只需要保持單調棧從底部到頭部是遞增即可

    public static int[][] getNearLessNoRepeat(int[] arr) {int[][] res = new int[arr.length][2];Stack<Integer> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {int popIndex = stack.pop();int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;res[popIndex][1] = i;}stack.push(i);}while (!stack.isEmpty()) {int popIndex = stack.pop();int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;res[popIndex][1] = -1;}return res;}

    此時來看看如果數組中有重復值
    如果有重復值,那么只需要把相同值的下標放在一起,串成一個鏈表!!

    比如: 此時來了一個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是否是正確的!!!

    public class Code03_AllTimesMinToMax {public static int max1(int[] arr) {int max = Integer.MIN_VALUE;for (int i = 0; i < arr.length; i++) {for (int j = i; j < arr.length; j++) {int minNum = Integer.MAX_VALUE;int sum = 0;for (int k = i; k <= j; k++) {sum += arr[k];minNum = Math.min(minNum, arr[k]);}max = Math.max(max, minNum * sum);}}return max;}public static int max2(int[] arr) {int size = arr.length;int[] sums = new int[size];sums[0] = arr[0];for (int i = 1; i < size; i++) {sums[i] = sums[i - 1] + arr[i];}int max = Integer.MIN_VALUE;Stack<Integer> stack = new Stack<Integer>();for (int i = 0; i < size; i++) {while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {int j = stack.pop();max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (sums[i - 1] - sums[stack.peek()])) * arr[j]);}stack.push(i);}while (!stack.isEmpty()) {int j = stack.pop();max = Math.max(max, (stack.isEmpty() ? sums[size - 1] : (sums[size - 1] - sums[stack.peek()])) * arr[j]);}return max;}public static int[] gerenareRondomArray() {int[] arr = new int[(int) (Math.random() * 20) + 10];for (int i = 0; i < arr.length; i++) {arr[i] = (int) (Math.random() * 101);}return arr;}public static void main(String[] args) {int testTimes = 2000000;for (int i = 0; i < testTimes; i++) {int[] arr = gerenareRondomArray();if (max1(arr) != max2(arr)) {System.out.println("FUCK!");break;}}}

    總結

    以上是生活随笔為你收集整理的单调栈的应用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。