算法筆記——單調(diào)棧

借鑒——單調(diào)棧總結(jié)/懦榧酰客網(wǎng)左神算法進(jìn)階班

基本問題

對(duì)于一個(gè)數(shù)組arr, 針對(duì)每個(gè)數(shù)允青,尋找它和它左 / 右邊第一個(gè)比它大 / 小的數(shù)的值,以及相距多少個(gè)數(shù)卵沉。

基本思路:

  • 創(chuàng)建一個(gè)棧颠锉,將數(shù)組中的元素下標(biāo)傳進(jìn)去。

  • 遍歷arr數(shù)組史汗,如果椖炯恚空,則入棧

  • 如果棧不為空淹办,且當(dāng)前棧頂元素值大于當(dāng)前arr遍歷到的位置的值,則直接入棧恶复。

  • 如果棧不為空怜森,且當(dāng)前棧頂元素值小于當(dāng)前arr遍歷到的位置的值,則棧頂元素出棧谤牡,棧頂元素的左邊第一個(gè)比它大以及右邊第一個(gè)比他大的元素就可以確定了

  • 如果棧不為空副硅,且當(dāng)前棧定元素值等于當(dāng)前arr遍歷到的位置的值,將具有相同值得數(shù)組索引下標(biāo)壓入一個(gè)鏈表或者數(shù)組翅萤。

  • 可以確定得是恐疲,只有當(dāng)元素出棧得時(shí)候,我們才可以獲取到當(dāng)前元素左邊以及右邊得信息套么。

  • 這里分別提供找出某個(gè)位置的右邊第一個(gè)大的數(shù)和左邊第一個(gè)大的數(shù)的方法培己。

代碼:

    // 問題一:給定一個(gè)數(shù)組,請(qǐng)你返回每個(gè)元素右邊第一個(gè)大于它的元素,如果不存在則返回-1
    public static int[] getFirstRightMax(int[] arr) {
        if(arr == null || arr.length == 0) return null;
        // 創(chuàng)建一個(gè)棧結(jié)構(gòu)
        Stack<Integer> stack = new Stack<Integer>();
        int[] res = new int[arr.length];
        int i = 0;
        while(i < arr.length) {
            if(stack.isEmpty() || arr[stack.peek()] >= arr[i]) {
                stack.push(i);
                i++;
            }else{
                res[stack.pop()] = arr[i];
            }
        }
        // 遍歷走完之后胚泌,棧中可能還剩余數(shù)組
        while(!stack.isEmpty()){
            res[stack.pop()] = -1;
        }
        return res;
    }

    // 問題二:給定一個(gè)數(shù)組省咨,請(qǐng)你返回每個(gè)元素左邊第一個(gè)大于它的元素,如果不存在則返回-1
    public static int[] getFirstLeftMax(int[] arr) {
        if(arr == null || arr.length == 0) return null;
        Stack<Integer> stack = new Stack<Integer>();
        int[] res = new int[arr.length];
        int i = 0;
        while(i < arr.length) {
            if(stack.isEmpty() || arr[stack.peek()] >= arr[i]) {
                stack.push(i);
                i++;
            }else{
                res[stack.pop()] = stack.isEmpty() ? -1 : arr[stack.peek()];
            }
        }
        while(!stack.isEmpty()){
            res[stack.pop()] = stack.isEmpty() ? -1 : arr[stack.peek()];
        }
        return res;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{1,5,3,6,4,8,9,10};
        int[] res1 = getFirstLeftMax(arr);
        int[] res2 = getFirstRightMax(arr);
        System.out.println(Arrays.toString(res1));
        System.out.println(Arrays.toString(res2));
    }

衍生問題一

LeeCode | 84. 柱狀圖中最大的矩形

給定 n 個(gè)非負(fù)整數(shù),用來表示柱狀圖中各個(gè)柱子的高度玷室。每個(gè)柱子彼此相鄰零蓉,且寬度為 1 。
求在該柱狀圖中穷缤,能夠勾勒出來的矩形的最大面積敌蜂。


柱狀圖中最大的矩形

基本思路:

  • 使用一個(gè)單調(diào)棧存儲(chǔ)一個(gè)單調(diào)遞增的序列,如果遇到大于等于棧頂元素的數(shù)組津肛,則入棧章喉,否則出棧,那么這么做的原因是什么尼?
  • 首先考慮出棧的過程囊陡,只有當(dāng)前元素小于棧頂元素或者當(dāng)前高度數(shù)組已經(jīng)遍歷完了才會(huì)出棧芳绩,那么出棧也就意味著最大面積maxArea的更新,這也滿足單調(diào)棧出棧更新的思想撞反。具體更新思想可能看圖更容易明白妥色。


    1
2
3

代碼如下:

    //LeeCode84 借用單調(diào)棧的思想求解柱狀圖中的最大矩形面積
    public static int largestRectangleArea(int[] heights) {
        // 首先進(jìn)行邊界條件判端
        if(heights == null || heights.length == 0) return 0;
        if(heights.length == 1) return heights[0];
        // 創(chuàng)建一個(gè)單調(diào)棧
        Stack<Integer> stack = new Stack<Integer>();
        int index = 0;
        int maxArea = 0;
        while(index < heights.length){
            if(stack.isEmpty()){
                stack.push(index++);
            }else{
                if(heights[stack.peek()] <= heights[index]){
                    stack.push(index++);
                }else{
                    // 取出當(dāng)前棧頂元素對(duì)應(yīng)的高度
                    int topHeight = heights[stack.pop()];
                    // 獲取左邊一個(gè)數(shù)據(jù)的索引,這個(gè)數(shù)據(jù)和當(dāng)前的index之間肯定可以構(gòu)成構(gòu)成高度為height的矩陣
                    int leftIndex = stack.isEmpty() ? -1 : stack.peek();
                    // 更新maxArea
                    maxArea = Math.max(maxArea,(index - leftIndex - 1) * topHeight);
                }
            }
        }
        // 循環(huán)遍歷結(jié)束時(shí)遏片,index == arr.length; 還需要將棧中剩余的數(shù)據(jù)全部考慮進(jìn)去
        while (!stack.isEmpty()) {
            int topHeight = heights[stack.pop()];
            int leftIndex = stack.isEmpty() ? -1 : stack.peek();
            maxArea = Math.max(maxArea,(index - leftIndex - 1) * topHeight);
        }
        return maxArea;
    }

衍生問題二——求二維數(shù)組中的最大矩形面積

LeetCode | 85. 最大矩形

題目描述:

最大矩形面積

基本思路:

  • 此題的基本思路就是就是建立在前一題的基礎(chǔ)上求解的嘹害。將二維數(shù)組中每一行看作直方圖的底,不斷更新直方圖中每個(gè)位置矩形的高度吮便,按照前一題的求解思路即可笔呀。

代碼如下:

// 給定二維數(shù)組,求二維數(shù)組中最大的矩形面積
// LeeCode85題
public class Code_04_MaxArea {

    public int maximalRectangle(char[][] matrix) {
        if(matrix == null || matrix.length == 0) return 0;
        int row = matrix.length;
        int col = matrix[0].length;
        int[] heights = new int[col];
        int maxArea = 0;
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                heights[j] = matrix[i][j] == '0' ? 0 : heights[j] + 1;
            }
            maxArea = Math.max(maxArea,getMaxArea(heights));
        }
        return maxArea;
    }

    /**
     * 給定一個(gè)直方圖的數(shù)組髓需,求該直方圖中最大的矩形面積
     * @param heights
     * @return
     */
    private int getMaxArea(int[] heights) {
        // 這里不進(jìn)行邊界判斷了许师,交給主函數(shù)
        // 創(chuàng)建單調(diào)棧
        Stack<Integer> stack = new Stack<Integer>();
        int index = 0;
        int maxArea = 0;
        while(index < heights.length){
            while(!stack.isEmpty() && heights[stack.peek()] >= heights[index]){
                int h = stack.pop();
                int l = stack.isEmpty() ? -1 : stack.peek();
                maxArea = Math.max(maxArea,(index - l - 1) * heights[h]);
            }
            stack.push(index++);
        }
        while(!stack.isEmpty()){
            int h =stack.pop();
            int l = stack.isEmpty() ? -1 : stack.peek();
            maxArea = Math.max(maxArea,(index - l - 1) * heights[h]);
        }
        return maxArea;
    }
}


衍生問題三 —— 構(gòu)造數(shù)組的MaxTree

定義二叉樹的節(jié)點(diǎn)如下:

public class Node{
    public int value;
    public Node left;
    public Node right;

    public Node(int value){
        this.value = value;
    }
}

一個(gè)數(shù)組的maxTree定義如下:

  1. 數(shù)組中必須沒有重復(fù)元素;
  2. maxTree是一顆二叉樹僚匆,數(shù)組中每一個(gè)值對(duì)應(yīng)一個(gè)二叉樹中的節(jié)點(diǎn)微渠。
  3. 包括maxTree在內(nèi)且在其中的每一顆子樹上,值最大的節(jié)點(diǎn)都是當(dāng)前樹的頭節(jié)點(diǎn)咧擂。
  4. 指定沒有重復(fù)元素的數(shù)組arr逞盆,寫出這個(gè)數(shù)組的maxTree生成函數(shù),要求如果數(shù)組長度為N松申,則時(shí)間復(fù)雜度為O(N), 空間復(fù)雜度為O(N).

基本思路:

  • 方法一: 將數(shù)組arr生成一個(gè)大頂堆云芦,然后構(gòu)造出一個(gè)完全二叉樹。

  • 方法二: 單調(diào)棧的思想贸桶,這里著重描述單調(diào)棧在這種問題中的求解思路舅逸。

  • 首先通過單調(diào)棧求出每個(gè)位置上左邊第一個(gè)比它大的元素,右邊第一個(gè)比他大的元素刨啸。

  • 如果某個(gè)位置上左右比他大的元素都沒有堡赔,則此位置的元素的值作為maxTree的根節(jié)點(diǎn)。

  • 如果某個(gè)位置上左右兩邊有一個(gè)為null设联, 那么當(dāng)前的元素作為此處參數(shù)信息非null的那個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn)善已。

  • 如果某個(gè)位置上左右兩邊的參數(shù)都存在,那么當(dāng)前的元素作為左右兩邊較大的那個(gè)值的子節(jié)點(diǎn)离例。


衍生題四——最大可見山峰對(duì)

問題描述:

指定一個(gè)數(shù)組arr换团;
數(shù)組的中元素的含義為一個(gè)環(huán)形山脈每座山的高度;
規(guī)定宫蛆,相鄰的兩座山脈之間可以彼此看見艘包;
如果兩座山脈不相鄰的猛,兩座山脈之間的所有山脈不大于這兩座山脈的最小值時(shí),這兩座山脈可以互相看見想虎。
現(xiàn)在希望你可以求出可以彼此看見的山脈總對(duì)數(shù)卦尊。

思路分析:

  • 首先找到數(shù)組中的最大元素壓入單調(diào)棧中。
  • 創(chuàng)建一個(gè)單調(diào)遞減的棧結(jié)構(gòu)
  • 如果待壓入元素不滿足單調(diào)性棧舌厨,則從棧中彈出元素岂却,更新結(jié)果集
  • 直到單調(diào)棧中元素更新結(jié)束,返回最終的結(jié)果集res裙椭。
1

2

3

代碼如下:

/**
 *
 * 左神算法進(jìn)階班第三課————單調(diào)棧
 * 難度——頂尖
 * 問題描述:
 *      指定一個(gè)數(shù)組arr躏哩;
 *      數(shù)組的中元素的含義為一個(gè)環(huán)形山脈每座山的高度;
 *      規(guī)定揉燃,相鄰的兩座山脈之間可以彼此看見扫尺;
 *      如果兩座山脈不相鄰,兩座山脈之間的所有山脈不大于這兩座山脈的最小值時(shí)炊汤,這兩座山脈可以互相看見。
 *      現(xiàn)在希望你可以求出可以彼此看見的山脈總對(duì)數(shù)抢腐。
 *
 */
public class Code_03_CircleMountain {

    static class Pair{
        int value;    // 存放當(dāng)前山峰的高度拨拓,就是數(shù)組中某個(gè)位置的值
        int times = 1;    // 當(dāng)前山峰已經(jīng)出現(xiàn)的次數(shù),也就是數(shù)組中某個(gè)位置元素出現(xiàn)的次數(shù)氓栈,初始化為1

        Pair(int value) {
            this.value = value;
        }
    }

    /**
     * 主函數(shù)入口,獲取彼此可以相互看見的最大山峰對(duì)
     * @param arr
     * @return
     */
    public static long communications(int[] arr) {
        if(arr == null || arr.length == 0) {
            return 0;
        }
        int size = arr.length;
        //-----更新出數(shù)組中最大元素的索引-----
        int maxIndex = 0;
        for (int i = 1; i < arr.length; i++) {
            maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex;
        }
        int value = arr[maxIndex];
        int index = nextIndex(size,maxIndex);
        long res = 0L;
        //-----創(chuàng)建單調(diào)棧的結(jié)構(gòu)-----
        Stack<Pair> stack = new Stack<Pair>();
        //----首先將數(shù)組中最大的元素扔進(jìn)棧中----
        stack.push(new Pair(value));
        while(index != maxIndex){
            value = arr[index];
            // 如果棧不為空婿着,且當(dāng)前遍歷到的元素大于棧頂?shù)脑刂凳谑荩敲创颂幰聄es,可以增加山峰對(duì)了
            while(!stack.isEmpty() && stack.peek().value < value) {
                int times = stack.pop().times;
                res += getInternalSum(times) + (2 * times);
            }
            if(!stack.isEmpty() && stack.peek().value == value){
                stack.peek().times++;
            }else{
                stack.push(new Pair(value));
            }
            index = nextIndex(size,index);
        }
        // 當(dāng)棧不為null時(shí)竟宋,繼續(xù)遍歷
        while(!stack.isEmpty()){
            int times = stack.pop().times;
            res += getInternalSum(times);
            if(!stack.isEmpty()){
                // 首先加上一個(gè)times
                res += times;
                if(stack.size() > 1){
                    //如果棧中剩余并不是最大的值提完,那么再加一個(gè)times,左右都可以找到最大的山峰
                    res += times;
                } else{
                    // 否則,如果最大的山峰只有一個(gè)丘侠,那么只需要加一次times即可
                    res += stack.peek().times > 1 ? times : 0;
                }
            }
        }
        return res;
    }

    /**
     * 獲取環(huán)中當(dāng)前位置的下一元素的索引
     * @param size
     * @param maxIndex
     * @return
     */
    private static int nextIndex(int size, int maxIndex) {
        return maxIndex == size - 1 ? 0 : maxIndex + 1;
    }

    /**
     * 獲取連續(xù)times相等的山峰可以構(gòu)成有效山峰對(duì)的數(shù)量
     * @param times
     * @return
     */
    private static long getInternalSum(int times) {
        return times == 1L ? 0 : (long)times * (long)(times - 1) / 2L;
    }

    // 測試函數(shù)
    public static void main(String[] args) {
        int[] arr = {1,2,3,3,3,4,4,5,5,6};
        System.out.println(communications(arr));
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徒欣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜗字,更是在濱河造成了極大的恐慌打肝,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挪捕,死亡現(xiàn)場離奇詭異粗梭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)级零,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門断医,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事鉴嗤≌镀簦” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵醉锅,是天一觀的道長兔簇。 經(jīng)常有香客問我,道長荣挨,這世上最難降的妖魔是什么男韧? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮默垄,結(jié)果婚禮上此虑,老公的妹妹穿的比我還像新娘。我一直安慰自己口锭,他們只是感情好朦前,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鹃操,像睡著了一般韭寸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上荆隘,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天恩伺,我揣著相機(jī)與錄音,去河邊找鬼椰拒。 笑死晶渠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的燃观。 我是一名探鬼主播褒脯,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼缆毁!你這毒婦竟也來了番川?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤脊框,失蹤者是張志新(化名)和其女友劉穎颁督,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浇雹,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡适篙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了箫爷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚷节。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡聂儒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出硫痰,到底是詐尸還是另有隱情衩婚,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布效斑,位于F島的核電站非春,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缓屠。R本人自食惡果不足惜奇昙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望敌完。 院中可真熱鬧储耐,春花似錦、人聲如沸滨溉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晦攒。三九已至闽撤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脯颜,已是汗流浹背哟旗。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留栋操,地道東北人热幔。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像讼庇,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子近尚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 題目 給定一個(gè)不含重復(fù)值的數(shù)組arr蠕啄,找到一個(gè)i位置左邊和右邊離i位置最近且值比arr[i]小的位置。返回所有位置...
    呤雪情楓閱讀 1,092評(píng)論 0 1
  • 冒泡排序 原理:比較兩個(gè)相鄰的元素戈锻,將值大的元素交換至右端歼跟。 思路:依次比較相鄰的兩個(gè)數(shù),將小數(shù)放在前面格遭,大數(shù)放在...
    yanghedada閱讀 1,095評(píng)論 0 0
  • 背景問題 給定長度為的數(shù)組哈街,其每個(gè)元素為非負(fù)整數(shù),計(jì)算其所有連續(xù)子序列的最小值之和 問題分析 首先可以很直觀的想到...
    AsuraLG閱讀 1,386評(píng)論 0 2
  • 1. 找出數(shù)組中重復(fù)的數(shù)字 題目:在一個(gè)長度為n的數(shù)組里的所有數(shù)字都在0到n-1的范圍內(nèi)拒迅。數(shù)組中某些數(shù)字是重復(fù)的骚秦,...
    BookThief閱讀 1,748評(píng)論 0 2
  • 第四天 數(shù)組【悟空教程】 第04天 Java基礎(chǔ) 第1章數(shù)組 1.1數(shù)組概念 軟件的基本功能是處理數(shù)據(jù)她倘,而在處理數(shù)...
    Java幫幫閱讀 1,589評(píng)論 0 9