常用算法(0)--單調(diào)棧法

1、概念

單調(diào)棧就是單調(diào)增或者單調(diào)減的棧。
在遍歷數(shù)組的的過程中將大(小于)于的元素存放在棧中,碰到小(大)于的元素,則全部出棧。

2、目的

為了減少一次循環(huán),將大(小)于的元素(或元素在數(shù)組中的下標)存放起來。
減少時間復雜度,增大了空間復雜度(棧的大小最大不超過要遍歷數(shù)組的長度)

3、應用場景

1、求左(右)邊第一個最大(最小)的數(shù)
2、面積問題

下面給出幾個實例,用作練習

1、實例:n人排隊向右看問題

問題:n人排隊,全部向右看,能看到各自低于和等于自己的人,看不到高于自己的人,求出所有人能看到人數(shù)的總和。
舉例:4,3,7,1(分別表示四人的身高),能看到的人數(shù)總和為2
分析:4能看到3,7能看到1,所以總數(shù)是2
傳統(tǒng)解法:循環(huán)兩次,第一次循環(huán)數(shù)組,第二次挨個比對
單調(diào)棧法:將比自己小的放入棧中,維護一個單調(diào)遞增棧,大數(shù)進來則小數(shù)出棧,始終維護棧的單調(diào)性
具體代碼:

public static int getSum(List<Integer> list){
    int sum =0;
    if(list.size()==0){
      return sum;
    }
    Stack<Integer> stack = new Stack<Integer>();
    list.add(Integer.MAX_VALUE);//設置邊界,保證最后元素可以出棧
    for (int i = 0; i < list.size(); i++) {
      if(stack.isEmpty()||list.get(i) <=list.get(stack.peek())){
        //空?;蛘咴匦∮跅m斣?,則入棧
        stack.push(i);
      }
      else{
        while (!stack.isEmpty()&& list.get(i)>list.get(stack.peek())){
          //保持棧的單調(diào)性,將小于當前元素的棧內(nèi)元素全部出棧,統(tǒng)計+1
          int top = stack.peek();
          stack.pop();
          sum += (i-top-1);
        }
        //當前元素入棧
        stack.push(i);
      }
    }
    return sum;
  }
2、實例:接雨水的問題(leetcode 42#)

問題:給定 n 個非負整數(shù)表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之后能接多少雨水。
舉例:

Marcos 貢獻此圖

上面是由數(shù)組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)
具體代碼:

public int trap(int[] height) {
        Stack<Integer> stack = new Stack<Integer>();
        int ans = 0;
        int i =0;
        if(height.length<=2){
            return 0;
        }
        while (i<height.length){
            while (!stack.empty() && height[i]>height[stack.peek()]){
                //如果當前元素比棧中元素大,則有可能會形成低洼,
                int top = stack.peek();
                stack.pop();
                if(stack.isEmpty()){
                    break;
                }
                //計算寬
                int distance = i - stack.peek()-1;
                //計算高(左邊的高度差和右邊的高度差中選擇一個最小的,木桶原理)
                int bounded_height = Math.min(height[i]-height[top],height[stack.peek()]-height[top]);
                //計算面積
                ans += distance* bounded_height;
            }
            stack.push(i);
            i++;
        }
        return ans;
    }
3、實例:柱狀圖最大矩陣面積

問題:給定 n 個非負整數(shù)表示柱狀圖中柱子的的高度,每人柱子彼此相鄰寬度為1,求在柱狀圖中能勾勒出的最大矩形的面積為多少。
舉例:

網(wǎng)絡圖形

上面是由數(shù)組 [2,1,5,6,2,3] 表示的高度圖,在這種情況下,最大矩形的面積是10(圖中陰影部分面積)
具體代碼:

private static int largestRect(List<Integer> arrs){
    Stack<Integer> stack = new Stack<Integer>();
    int ans = 0;
    stack.push(-1);
    arrs.add(0);
    for (int i = 0; i < arrs.size(); i++) {
      while (!stack.isEmpty() && stack.peek()!=-1 && arrs.get(i)<arrs.get(stack.peek())){
        int top = stack.peek();
        stack.pop();
        //矩形的寬
        int a = (i-stack.peek()-1);
        //矩形的高
        int b = arrs.get(top);
        //矩形的面積
        int area = a*b;
        ans = Math.max(area,ans);
      }
      stack.push(i);
    }
    return ans;
  }
4、實例:最大矩陣面積

問題:給定一個整形矩陣的map,其中的值只有0和1,求其中全是1構(gòu)成的所有矩形區(qū)域中最大的區(qū)域面積,即所有全由1構(gòu)成的區(qū)域中1的個數(shù)最多的區(qū)域。
舉例:

網(wǎng)絡圖形

問題分析:此問題這樣考慮,依次將每一行作為底,建立一個如實例3中的柱狀圖,然后統(tǒng)計出每次得出的柱狀圖中最大面積的矩形。最后在得到的行數(shù)個最大面積的矩形中求出最大值。
具體分析:以上圖的3*4矩陣為例,遍歷過程如下圖:
遍歷流程圖

具體代碼:

public static int maximalRectangle(char[][] matrix) {
    if(null == matrix || 0 == matrix.length)
      return 0;
    int m = matrix.length, n = matrix[0].length;
    int[][] p = new int[m][n];
    int maxS = 0;
    int[] arr = new int[n];
    //依次以每行為底,構(gòu)建柱形圖中的柱形數(shù)組
    for(int i = 0; i < m; i++) {
      for (int j = 0; j < n; j++) {
        if(matrix[i][j] == '0'){
          arr[j]=0;
        }else{
          int a = arr[j];
          arr[j] += 1;
        }
      }
      maxS = Math.max(maxS,largestRect(arr));
    }
    return maxS;
  }

private static int largestRect(int[] arr){
    Stack<Integer> stack = new Stack<Integer>();
    int ans = 0;
    stack.push(-1);
    //給數(shù)組最后一位補充0,目的是為了計算最后一個矩形的面積
    int[] arrs = new int[arr.length+1];
    for (int i = 0; i < arr.length; i++) {
      arrs[i]=arr[i];
    }
    arrs[arr.length] = 0;
    for (int i = 0; i < arrs.length; i++) {
      while (!stack.isEmpty() && stack.peek()!=-1 && arrs[i]<arrs[stack.peek()]){
        int top = stack.peek();
        stack.pop();
        //矩形的寬
        int a = (i-stack.peek()-1);
        //矩形的高
        int b = arrs[top];
        //矩形的面積
        int area = a*b;
        ans = Math.max(area,ans);
      }
      stack.push(i);
    }
    return ans;
  }
5、實例:烽火相望

問題:給你一個數(shù)組,數(shù)組中的每個數(shù)代表一座山的高度,這個數(shù)組代表將數(shù)組中的數(shù)從頭到尾連接而成的環(huán)形山脈。比如數(shù)組[2,1,3,4,5]形成的環(huán)形山脈如下
舉例:

圖片來自網(wǎng)絡

其中藍色的圓圈就代表一座山,圈中的數(shù)字代表這座山的高度。現(xiàn)在在每座山的山頂都點燃烽火,假設你處在其中的一個山峰上,要想看到另一座山峰的烽火需滿足以下兩個條件中的一個:
1、相鄰。比如A可以看到B,E
2、如果不相鄰,兩個山峰中間的山峰均不高于這兩個山峰,則這兩個山峰可以互相看見,否則看不見。
問:所有山峰中,能互相看到烽火的兩兩山峰的對數(shù)。
以[2,1,3,4,5]為例,
能互相看見的有:(2,1)(1,3)(3,4)(4,5)(5,2)(2,3)(3,5),共7對
問題分析:
1、如果元素不重復,則去除最高的和次高的,剩下的N-2個都可以看到最高的次高的。則可以組成2(N-2)對,然后加上最高和次高組一隊,總共2(N-2)+1=2N-3次
2、如果存在重復元素,則不符合以上的計算方法,假如4個山峰高都為2,則組合應該是在4個中隨機選取2個,所以是C(4,2) =6。這種情況我們可以使用單調(diào)棧來解決
具體代碼:



參考資料
1、單調(diào)棧玩法---烽火臺
2、單調(diào)棧算法筆記
3、[數(shù)據(jù)結(jié)構(gòu)]——單調(diào)棧
4、單調(diào)棧類型總結(jié)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • C 程序步驟中的一些問題 寫C 和 C++ 或者 python 還是有很多不同,列舉一下常見的基礎流程的問題 ma...
    fatesnight閱讀 753評論 0 1
  • 一.Array對象方法 Array 對象方法 .concat()[https://segmentfault.com...
    Angel_6c4e閱讀 794評論 0 0
  • 《網(wǎng)絡》 在瀏覽器中輸入url,回車發(fā)生了什么DNS的分級查找3次握手都發(fā)送了什么數(shù)據(jù)包設計路由表查找算法,滿足最...
    _ifndef閱讀 600評論 0 2
  • 本文轉(zhuǎn)自https://blog.csdn.net/Oliverfly1/article/details/1071...
    Nrocinu閱讀 359評論 0 0
  • 0 HTML5相關 websocket WebSocket 使用ws或wss協(xié)議,Websocket是一個持久化的...
    可愛多小姐閱讀 1,008評論 0 0

友情鏈接更多精彩內(nèi)容