【刷题系列】栈

系列汇总:《刷题系列汇总》



——————《剑指offeer》———————

1. 用两个栈实现队列

  • 题目描述:用两个栈来实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

示例:
输入:
[“PSH1”,“PSH2”,“POP”,“POP”]
返回:
1,2
解析:
“PSH1”:代表将1插入队列尾部
“PSH2”:代表将2插入队列尾部
"POP“:代表删除一个元素,先进先出=>返回1
"POP“:代表删除一个元素,先进先出=>返回2

  • 优秀思路:负负得正,【a b c】→第一次入栈→【c b a】→第二次入栈→【a b c】,故两次入栈即实现队列的先进先出
import java.util.Stack;
public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
 
    public void push(int node) {
        stack1.push(node);
    }
 
    public int pop() {
        if (stack2.isEmpty()) { // stack2 为空
            while (stack1.size() != 0) {
                stack2.push(stack1.pop()); // stack1全部压入stack2
            }
        }
        return stack2.pop(); // stack2 不为空直接弹出
    }
}

2. 包含min函数的栈(没看懂)

  • 题目描述:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,并且调用 min函数、push函数 及 pop函数 的时间复杂度都是 O(1)
    • push(value):value压入栈中
    • pop():弹出栈顶元素
    • top():获取栈顶元素
    • min():获取栈中最小元素
  • 优秀思路
import java.util.Stack;
//使用一个同步辅助栈
public class Solution {
    private Stack<Integer> s1 = new Stack();
    private Stack<Integer> s2 = new Stack();
    public void push(int node) {
        s1.push(node);
        if(s2.isEmpty()){
            s2.push(node);
        }else{
            s2.push(Math.min(s2.peek(), node));
        }
    }
    public void pop() {
        s1.pop();
        s2.pop();
    }
    public int top() {
       return s1.peek();
    }
    public int min() {
        return s2.peek();
    }
}

3. 栈的压入、弹出序列

  • 题目描述:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
  • 优秀思路:【模拟法】新建一个栈按给出的入栈和出栈顺序走一遍,看能不能走的通即可
    • 我的实现(86%,优秀):【双指针+模拟栈】
import java.util.*;
public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA.length == 0 || popA.length == 0) return true;
        Stack<Integer> stack = new Stack<>();
        int p1 = 0, p2 = 0;
        while(p1 < pushA.length){
            stack.push(pushA[p1]);
            p1++;
            while(!stack.isEmpty() && stack.peek() == popA[p2]){
                stack.pop();
                p2++;
            }
        }
        if(!stack.isEmpty()) return false;
        return true;
    }
}

——————《LeectCode》———————

1. 直方图的水量

  • 题目描述:给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 1。
    在这里插入图片描述

  • 我的思路(99.93%):【有效积水区域法】① 从左到右遍历,记录当前最高柱子索引,若遇到更高的柱子,则说明构成了有效积水区域,当前积水有效,加入结果;② 若最高柱子出现在中间,则其后面的积水无法统计。故还需从右边向左遍历到最高柱子处,按相同方法计算积水面积 。

class Solution {
    public int trap(int[] height) {
        if(height == null || height.length <= 2) return 0;
        int res = 0, curMaxHeightInd_left = 0;
        int tempWater = 0;
        for(int i = 0;i < height.length;i++){
            if(height[i] < height[curMaxHeightInd_left]){
                tempWater += height[curMaxHeightInd_left] - height[i];
            }else{ // 遇到更高的柱子(构成了有效积水区域),当前积水有效,加入结果
                curMaxHeightInd_left = i;
                res += tempWater;
                tempWater = 0;
            }
        }
        // 若最高柱子出现在中间,则其后面的积水无法统计
        // 故还需从右边向左遍历到最高柱子处,按相同方法计算积水面积
        int curMaxHeightInd_right = height.length-1;
        tempWater = 0;
        for(int i = height.length-1;i >= curMaxHeightInd_left;i--){
            if(height[i] < height[curMaxHeightInd_right]){
                tempWater += height[curMaxHeightInd_right] - height[i];
            }else{
                curMaxHeightInd_right = i;
                res += tempWater;
                tempWater = 0;
            }
        }
        return res;
    }
}
  • 相似思路(99%,更简洁):【先加后减】① 只要是比当前最高柱子矮出的面积都加入答案(所以会额外加上一些并没有构成积水区域的积水面积);② 从右边往左遍历到最高柱子处:减去额外多记录的超出当前柱子的积水面积
class Solution {
    public int trap(int[] height) {
        int sum = 0;
        // l : 记录当前最高柱子的索引
        int l = 0, r = height.length - 1;
        for (int i = l; i < height.length; i++) {
            if (height[i] >= height[l]) l = i;
            sum += height[l] - height[i]; // 只要是比当前最高柱子矮出的面积都加入答案(所以会额外加上一些并没有构成积水区域的积水面积)
        }
        // 从右边往左遍历到最高柱子处:减去额外多记录的超出当前柱子的积水面积
        for (int i = r; i > l; i--) {
            if (height[i] >= height[r]) r = i;
            sum -= height[l] - height[r]; // 最高柱子-当前柱子 即为额外记录的部分
        }
        return sum;
    }
}
  • 优秀思路1(44%):【单调栈】维护一个单调栈,单调栈存储的是下标,满足从栈底到栈顶的下标对应的数组height 中的元素递减。
    • ① 从左到右遍历数组,遍历到下标 i 时,如果栈内至少有两个元素,记栈顶元素为top,top 的下面一个元素是left,则一定有height[left]≥height[top]。
    • ② 如果height[i]>height[top],则得到一个可以接雨水的区域,该区域的宽度是i−left−1,高度是min(height[left],height[i])−height[top],根据宽度和高度即可计算得到该区域能接的水的量。
    • ③ 为了得到 left,需要将top 出栈。在对 top 计算能接的水的量之后,left 变成新的top,重复上述操作,直到栈变为空,或者栈顶下标对应的 height 中的元素大于或等于 height[i]。
    • 在对下标 i处计算能接的水的量之后,将 i入栈,继续遍历后面的下标,计算能接的水的量。遍历结束之后即可得到能接的水的总量。
class Solution {
    public int trap(int[] height) {
        int ans = 0;
        Deque<Integer> stack = new LinkedList<Integer>();
        int n = height.length;
        for (int i = 0; i < n; ++i) {
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int top = stack.pop();
                // 遍历完成
                if (stack.isEmpty()) {
                    break;
                }
                int left = stack.peek();
                int currWidth = i - left - 1;
                int currHeight = Math.min(height[left], height[i]) - height[top];
                ans += currWidth * currHeight;
            }
            stack.push(i);
        }
        return ans;
    }
}

2. 反转每对括号间的子串(需重写)

  • 题目描述:给出一个字符串 s(仅含有小写英文字母和括号)。请你按照从括号内到外的顺序,逐层反转每对匹配括号中的字符串,并返回最终的结果。注意,您的结果中 不应 包含任何括号。
  • 我的思路:找到规律,奇数层的先处理该层后面部分再处理前面部分,偶数层的先处理前面部分再处理后面部分,下面代码不适用于括号没有嵌套的情形,只适用于完全嵌套的情况
class Solution {
    public String reverseParentheses(String s) {
        if(s == null) return null;
        List<Character> list = new ArrayList<>();
        int countBracket = 0;
        int countLeft = 0,countRIght = 0;
        int p1 = 0,p2 = s.length()-1;
        // 先加入括号外的
        while(p1 < p2){
            for(int i = p1;i <= p2;i++){
                if(p1 == 0 && s.charAt(p1) != '('){
                    while(p1 < s.length() && s.charAt(p1) != '(' && s.charAt(p1) != ')'){
                        list.add(s.charAt(p1));
                        countLeft++;
                        p1++;
                        i++;
                    }
                }else if(s.charAt(i) == '('){ //遇到左括号
                    if(p2 == s.length()-1 && s.charAt(p2) != ')'){
                        while(s.charAt(p2) != ')' && s.charAt(p2) != '('){
                            list.add(list.size()-countRIght,s.charAt(p2));
                            countRIght++;
                            p2--;
                        }
                    }
                    countBracket++;
                    if(countBracket % 2 != 0){ //奇数层,处理顺序:右部分→左部分
                        while(s.charAt(--p2) != ')' && s.charAt(p2) != '('){ // ① 在list头部加入当前层的右部分字母
                            list.add(countLeft,s.charAt(p2));
                            countLeft++;
                        }
                        if(p1 >= p2) break;
                        // ② 在list尾部加入当前层的左部分字母
                        while(s.charAt(++p1) != '(' && s.charAt(p1) != ')'){
                            list.add(list.size()-countRIght,s.charAt(p1));
                            countRIght++;
                            i++;
                        }
                    }else{ // 处理顺序:左部分→右部分
                        while(s.charAt(++p1) != '(' && s.charAt(p1) != ')'){
                            list.add(countLeft,s.charAt(p1));
                            countLeft++;
                            i++;
                        }
                        if(p1 >= p2) break;
                        while(s.charAt(--p2) != ')' && s.charAt(p2) != '('){
                            list.add(list.size()-countRIght,s.charAt(p2));
                            countRIght++;
                        }
                    }
                }
            }
        }
        StringBuffer sb = new StringBuffer();
        for(char c:list){
            sb.append(c);
        }
        return sb.toString();
    }
}
  • 优秀思路1:【队列】我们从左到右遍历该字符串,使用字符串 str 记录当前层所遍历到的小写英文字母。对于当前遍历的字符:如果是左括号,将 str 插入到栈中,并将str 置为空,进入下一层;如果是右括号,则说明遍历完了当前层,需要将str 反转,返回给上一层。具体地,将栈顶字符串弹出,然后将反转后的str 拼接到栈顶字符串末尾,将结果赋值给str。如果是小写英文字母,将其加到str 末尾。
class Solution {
    public String reverseParentheses(String s) {
        Deque<String> q = new LinkedList<String>();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            // 遇到左括号:存储当前字符串,清空当前字符串
            if (ch == '(') {
                q.push(sb.toString());
                sb.setLength(0); // 清空sb
            }
            // 遇到右括号:翻转当前字符串,并将上一层的字符串出队列拼接在当前层前面
            else if (ch == ')') {
                sb.reverse();
                sb.insert(0, q.pop()); // 
            }
            // 遇到字母:存储 
            else {
                sb.append(ch);
            }
        }
        return sb.toString();
    }
}
  • 优秀思路2:【预处理括号+左右跳转】和我的思路大概一致,沿着某个方向移动,此时遇到了括号,那么我们只需要首先跳跃到该括号对应的另一个括号所在处,然后改变移动方向即可。这个方案同时适用于遍历时进入更深一层,以及完成当前层的遍历后返回到上一层的方案。在实际代码中,我们需要预处理出每一个括号对应的另一个括号所在的位置,这一部分我们可以使用栈解决。当我们预处理完成后,即可在线性时间内完成遍历,遍历的字符串顺序即为反转后的字符串。
class Solution {
    public String reverseParentheses(String s) {
        int n = s.length();
        int[] pair = new int[n];
        Deque<Integer> stack = new LinkedList<Integer>();
        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else if (s.charAt(i) == ')') {
                int j = stack.pop();
                pair[i] = j;
                pair[j] = i;
            }
        }

        StringBuffer sb = new StringBuffer();
        int index = 0, step = 1;
        while (index < n) {
            if (s.charAt(index) == '(' || s.charAt(index) == ')') {
                index = pair[index];
                step = -step;
            } else {
                sb.append(s.charAt(index));
            }
            index += step;
        }
        return sb.toString();
    }
}

3. 每日温度

  • 题目描述:请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]
    • 提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。
  • 优秀思路:【单调栈】维护一个单调栈,存储温度的索引值。当当前温度大于栈顶温度时,为栈顶索引计算天数,并将栈顶出栈,当前温度入栈。小于栈顶温度则直接入栈。在这里插入图片描述在这里插入图片描述
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int length = temperatures.length;
        int[] ans = new int[length];
        Deque<Integer> stack = new LinkedList<Integer>();
        for (int i = 0; i < length; i++) {
            int temperature = temperatures[i];
            while (!stack.isEmpty() && temperature > temperatures[stack.peek()]) {
                int prevIndex = stack.pop();
                ans[prevIndex] = i - prevIndex;
            }
            stack.push(i);
        }
        return ans;
    }
}

4. 删除字符串中的所有相邻重复项

  • 题目描述:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
  • 我的思路(63%):【栈】① 建立栈删除重复元素;② 将栈元素存到StringBuffer中;③ 将StringBuffer反序后转换为String输出:之所以要反序是因为栈后入先出
class Solution {
    public String removeDuplicates(String s) {
        if(s == null) return null;
        Deque<Character> stack = new ArrayDeque<>();
        StringBuffer sb = new StringBuffer();
        for(char c:s.toCharArray()){
            if(stack.isEmpty() || stack.peek() != c){
                stack.push(c);
            }else stack.pop();
        }
        for(char c:stack){
            sb.append(c);
        }
        return sb.reverse().toString(); // 由于栈后入先出的特性,需要先颠倒一下
    }
}
  • 优秀思路1(81%):【利用栈思想】不建立真正的栈,直接用StringBuffer模拟栈
class Solution {
    public String removeDuplicates(String s) {
        StringBuffer stack = new StringBuffer();
        int top = -1; // 存储栈顶元素的位置,小于0说明栈为空
        for (int i = 0; i < s.length(); ++i) {
            char ch = s.charAt(i);
            // 栈不为空 且 栈顶==当前
            if (top >= 0 && stack.charAt(top) == ch) {
                stack.deleteCharAt(top);
                --top;
            } else {
                stack.append(ch);
                ++top;
            }
        }
        return stack.toString();
    }
}
  • 优秀思路2(99%):【妙!坐标回退法】直接在原字符串上进行修改,核心思想时维护一个索引top(初始-1),当遇到相同值时,索引回退1,当遇到不同值时,修改top+1处的字符

举例:”abbacd“
① top = -1
② top = 0,a
③ top = 1,ab
④ 遇到 b= b,top-1 = 0
⑤ 遇到 a = a,top - 1 = -1
⑥ top+1 = 0,c
⑦ top+1 = 1,cd

class Solution {
    public String removeDuplicates(String S) {
        char[] s = S.toCharArray();
        int top = -1;
        for (int i = 0; i < S.length(); i++) {
            if (top == -1 || s[top] != s[i]) {
                s[++top] = s[i];
            } else {
                top--;
            }
        }
        return String.valueOf(s, 0, top + 1);
    }
}

5. 132 模式

  • 题目描述:给你一个整数数组 nums ,数组中共有 n 个整数。132 模式的子序列 由三个整数 nums[i]、nums[j]nums[k] 组成,并同时满足:i < j < knums[i] < nums[k] < nums[j] 。如果 nums 中存在 132 模式的子序列 ,返回 true ;否则,返回 false
  • 优秀思路:【单调栈-枚举法】从后往前遍历,维护一个单调递减栈存储j。只要当前值小于栈顶最小值,则将最小值出栈赋给k,只要存在nums[i]<k,则返回true
    • 注意1k初始MIN_VALUE,故只要k存在其他值,一定是出栈来的,故站内一定存在比k大的j,即保证了[j,k]结构的存在,所以只要前面有比k小的i,则说明132结构存在
    • 注意2:由于单调递减栈出栈采用的while循环,可以保证当前的k一定是仅次于最大值的数
class Solution {
    public boolean find132pattern(int[] nums) {
        int n = nums.length;
        // 单调递减栈,从后往前遍历,随时存储最大值
        // 若有值被弹出,则赋给k
        Deque<Integer> d = new ArrayDeque<>();
        int k = Integer.MIN_VALUE;
        for (int i = n - 1; i >= 0; i--) {
            if (nums[i] < k) return true;
            while (!d.isEmpty() && d.peekLast() < nums[i]) {
                k = d.pollLast(); 
            }
            d.addLast(nums[i]);
        }
        return false;
    }
}

6. 扁平化嵌套列表迭代器(没看懂)

  • 题目描述:给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。
  • 优秀思路:【栈+dfs】可以用一个栈来代替方法一中的递归过程。具体来说,用一个栈来维护深度优先搜索时,从根节点到当前节点路径上的所有节点。由于非叶节点对应的是一个列表,我们在栈中存储的是指向列表当前遍历的元素的指针(下标)。每次向下搜索时,取出列表的当前指针指向的元素并将其入栈,同时将该指针向后移动一位。如此反复直到找到一个整数。循环时若栈顶指针指向了列表末尾,则将其从栈顶弹出。
public class NestedIterator implements Iterator<Integer> {
    // 存储列表的当前遍历位置,而不是元素值
    private Deque<Iterator<NestedInteger>> stack; // 栈的泛型为该类实现的接口

    public NestedIterator(List<NestedInteger> nestedList) {
        stack = new LinkedList<Iterator<NestedInteger>>(); // 栈的泛型为该类实现的接口
        stack.push(nestedList.iterator()); // nestedList.iterator()表示目标列表nestedList在迭代器中的初始位置,即第一个元素处
    }

    @Override
    public Integer next() {
        // 由于保证调用 next 之前会调用 hasNext,直接返回栈顶列表的当前元素
        return stack.peek().next().getInteger(); //getInteger()为内置函数,功能为获取列表当前位置的整数值
    }

    @Override
    public boolean hasNext() {
        while (!stack.isEmpty()) {
            Iterator<NestedInteger> it = stack.peek();
            // 递归获得下一位置的状态
            // 1、遍历到当前列表末尾:出栈
            if (!it.hasNext()) { 
                stack.pop();
                continue;
            }
            // 2、未到末尾:若取出的元素是整数,则通过创建一个额外的列表将其重新放入栈中
            NestedInteger next = it.next(); // 下一元素
            if (next.isInteger()) { // isInteger() 为内置函数,功能为判断当前位置的列表元素是否为整数
                List<NestedInteger> list = new ArrayList<NestedInteger>();
                list.add(next);
                stack.push(list.iterator());
                return true;
            }
            stack.push(next.getList().iterator());
        }
        return false;
    }
}

7. 去除重复字母

  • 题目描述:给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
  • 优秀思路
class Solution {
    public String removeDuplicateLetters(String s) {
    	// 数组记录每个字符出现的次数和在字符串中存在的状态
        boolean[] vis = new boolean[26];
        int[] num = new int[26];
        for (int i = 0; i < s.length(); i++) {
            num[s.charAt(i) - 'a']++; // 下标表示字母序数(char-'a'),值表示出现次数
        }
		// 单调递减栈记录最终返回的字符串
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            // 当其在字符串中不存在时
            if (!vis[ch - 'a']) {
            	// 删除字符串尾部那些字典序大于ch的字母
                while (sb.length() > 0 && sb.charAt(sb.length() - 1) > ch) {
                	// 直至某字母出现次数 = 0,即后面没了则保留
                    if (num[sb.charAt(sb.length() - 1) - 'a'] > 0) {
                        vis[sb.charAt(sb.length() - 1) - 'a'] = false;
                        sb.deleteCharAt(sb.length() - 1);
                    } else {
                        break;
                    }
                }
                // 删除结束后添加该字母到队尾
                vis[ch - 'a'] = true; // 更新其存在状态
                sb.append(ch);
            }
            // 无论是否存在,出现次数都-1
            num[ch - 'a'] -= 1;
        }
        return sb.toString();
    }
}

8. 移掉 K 位数字

  • 题目描述:给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
    在这里插入图片描述
  • 我的思路(优秀,86%):【贪心+单调栈】利用StringBuffer模拟一个单调递增栈stack,若当前数字cur >= 串尾数字last,则直接入栈。否则删去所有 last删除停止的条件有三个:① stack为空;② 长度条件:字符串再删除就不够要求的长度了;③ last ≤ cur。是否添加元素到队尾的条件长度+1 ≤ len-k(最大长度)。由于最终字符串中前面可能含0,故从第一位非0数字开始输出

    长度边界推导:
    sb-1+(剩余长度)= sb-1+(len-i)>= 要求长度=len-k
    两边约去得 sb+k-i >= 1,即 sb+k-i > 0

class Solution {
    public String removeKdigits(String num, int k) {
        if(k == 0) return num;
        if(k == num.length()) return "0";
        int len = num.length();
        StringBuffer sb = new StringBuffer();
        for(int i = 0;i < len;i++){
            char c = num.charAt(i);
            // 长度边界推导:sb-1+(剩余长度)= sb-1+(len-i)>= 要求长度=len-k
            //             两边约去得 sb+k-i >= 1,即 sb+k-i > 0
            while(sb.length() > 0 && sb.length() + k - i > 0 && sb.charAt(sb.length()-1) > c){
                sb.deleteCharAt(sb.length()-1);
            }
            if(sb.length() < len-k){ // 未超过最大长度
                sb.append(c);
            }
        }
        String res = sb.toString();
        int i = 0;
        while(i < res.length()){
            if(res.charAt(i) != '0') break;
            i++;
        }
        // 从第一位非0数字开始输出
        return i == res.length()? "0":res.substring(i); // 满足条件说明字符串中没有非零数字
    }
}

9. 柱状图中最大的矩形(全场最快)

  • 题目描述:给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
    在这里插入图片描述

  • 我的思路(优秀,94%):总体思路:遍历数组,每次以当前柱子的高度curHeight为矩形的高度,左右搜索其他高度不低于curHeight的柱子加入矩形,将当前柱子高度和搜索到的矩形面积中的最大值用于更新结果res。核心:设置一个数组记录preHeight每根柱子上次被搜索时矩形的高度,避免重复搜索。当遇到某根柱子的高度 = 该柱子上次被搜索过的矩形高度时则直接跳过。详情请参考我自己写的题解

class Solution {
    int[] preHeight; 
    public int largestRectangleArea(int[] heights) {
        // 特殊情况
        if(heights.length == 0) return 0;
        if(heights.length == 1) return heights[0];
        
        preHeight = new int[heights.length]; // 存储每根柱子上次被横向搜索时的矩阵高度
        int res = 0;
        for(int i = 0;i < heights.length;i++){
            if(heights[i] != preHeight[i]){
                res = Math.max(res,Math.max(heights[i],transverseMaximumArea(heights,i))); 
            }
        }
        return res;
    }
    // 横向搜索到的矩阵面积:以当前柱子的高度为矩形的高度,左右横向搜索矩形其他部分
    private int transverseMaximumArea(int[] heights,int position){
        int leftLen = 0; // 左边高度不低于当前柱子的柱子数量
        int rightLen = 0; // 右边高度不低于当前柱子的柱子数量
        preHeight[position] = heights[position];
        while(leftLen+1 <= position && heights[position-(leftLen+1)] >= heights[position]){ //搜索左边高度不低于当前柱子的柱子
            preHeight[position-(leftLen+1)] = heights[position];
            leftLen++;
        }
        while(position+rightLen+1 < heights.length && heights[position+(rightLen+1)] >= heights[position]){//搜索右边高度不低于当前柱子的柱子
            preHeight[position+(rightLen+1)] = heights[position];
            rightLen++;
        }
        return heights[position]*(1+leftLen+rightLen); // 搜索到的矩阵面积:高*宽
    }
}
  • 优秀思路2(99%):【双指针】与我的思路类似,但避免重复搜索的方式更快更简洁
class Solution {
    public int largestRectangleArea(int[] heights) {
        int maxarea = 0;
        int len = heights.length;
        for (int i = 0;i < len;i++){
            if (heights[i]*len <= maxarea) continue;// 如果当前元素的值 × 长度都小于当前maxarea则没有继续查找的必要
            int left = i;
            int right = i;
            while (left-1>=0 && heights[left-1] >= heights[i]) left--;
            while (right+1 < len && heights[right+1] >= heights[i]) right++;
            int area = (right-left+1)*heights[i];
            if (area > maxarea) maxarea = area;
        }
        return maxarea;
    }
}
  • 优秀思路3(86%):【单调栈】遍历每个柱体,若当前的柱体高度大于等于栈顶柱体的高度,就直接将当前柱体入栈,否则若当前的柱体高度小于栈顶柱体的高度,说明当前栈顶柱体找到了右边的第一个小于自身的柱体,那么就可以将栈顶柱体出栈来计算以其为高的矩形的面积了。
class Solution {
    public int largestRectangleArea(int[] heights) {
        // 为了代码简便,在柱体数组的头和尾加了两个高度为 0 的柱体。
        int[] tmp = new int[heights.length + 2];
        System.arraycopy(heights, 0, tmp, 1, heights.length); // 将heights从0开始的部分复制到tmp中1开始的部分
        
        Deque<Integer> stack = new ArrayDeque<>(); // 存储的是下标
        int area = 0;
        for (int i = 0; i < tmp.length; i++) {
            // 对栈中柱体来说,其底下第一个柱体就是其「左边第一个小于自身的柱体」;
            // 若当前柱体 i 的高度小于栈顶柱体的高度,说明 i 是栈顶柱体的「右边第一个小于栈顶柱体的柱体」。
            // 因此以栈顶柱体为高的矩形的左右宽度边界就确定了,可以计算面积🌶️ ~
            while (!stack.isEmpty() && tmp[i] < tmp[stack.peek()]) {
                int h = tmp[stack.pop()]; // 栈顶出栈
                area = Math.max(area, (i - stack.peek() - 1) * h);   
            }
            stack.push(i);
        }
        return area;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值