Leetcode百题斩-栈

发布于:2025-07-01 ⋅ 阅读:(24) ⋅ 点赞:(0)

终于来到了栈专题,想想之前来阿里的时候就是面试了一道栈最终通过了终面,也是十分怀念了。

739. Daily Temperatures[Medium]

思路:这就是最典型的单调栈问题了。从后向前维护下一个更大值或者下一个更大值的位置。

可以看一下当年面阿里时的博客,一切都还记忆犹新

单调栈专题练--下一个更大元素-CSDN博客

至于栈的数据结构,先尝试了使用java自身的数据结构Stack,但确实慢的让人发指,可能其内部各种并行安全机制拖慢了其本身的性能

/*
Author Owen_Q
*/
public class DailyTemperature {

    public static int[] dailyTemperatures(int[] temperatures) {
        int len = temperatures.length;
        Deque<Integer> stack = new ArrayDeque<>();
        int[] result = new int[len];
        for (int i = len - 1; i >= 0; i--) {
            while (!stack.isEmpty() && temperatures[i] >= temperatures[stack.peek()]) {
                stack.pop();
            }
            if (!stack.isEmpty()) {
                result[i] = stack.peek() - i;
            }
            stack.push(i);
        }
        return result;
    }
}

多想一下:用链表实现栈

再尝试用链表LinkedList来实现栈,速度要快了很多 

/*
Author Owen_Q
*/
public class DailyTemperature {

    public static int[] dailyTemperaturesWithLinkedList(int[] temperatures) {
        int len = temperatures.length;
        List<Integer> stack = new LinkedList<>();
        int[] result = new int[len];
        for (int i = len - 1; i >= 0; i--) {
            while (!stack.isEmpty() && temperatures[i] >= temperatures[stack.get(stack.size() - 1)]) {
                stack.remove(stack.size() - 1);
            }
            if (!stack.isEmpty()) {
                result[i] = stack.get(stack.size() - 1) - i;
            }
            stack.add(i);
        }
        return result;
    }
}

多想一下:用双端队列来实现栈

Qwen了一下,ai推荐用ArrayDeque,那么来试试,确实比链表还要快一些

/*
Author Owen_Q
*/
public class DailyTemperature {

    public static int[] dailyTemperaturesWithDeque(int[] temperatures) {
        int len = temperatures.length;
        Deque<Integer> stack = new ArrayDeque<>();
        int[] result = new int[len];
        for (int i = len - 1; i >= 0; i--) {
            while (!stack.isEmpty() && temperatures[i] >= temperatures[stack.peek()]) {
                stack.pop();
            }
            if (!stack.isEmpty()) {
                result[i] = stack.peek() - i;
            }
            stack.push(i);
        }
        return result;
    }
}

多想一下:用原始数组手动实现

一切花里胡哨都不如自己手撕,还是手撕大法好呀

/*
Author Owen_Q
*/
public class DailyTemperature {

    public static int[] dailyTemperaturesManual(int[] temperatures) {
        int len = temperatures.length;
        int[] stack = new int[len];
        int top = -1;
        int[] result = new int[len];
        for (int i = len - 1; i >= 0; i--) {
            while (top>=0 && temperatures[i] >= temperatures[stack[top]]) {
                top--;
            }
            if (top>=0) {
                result[i] = stack[top] - i;
            }
            stack[++top]=i;
        }
        return result;
    }
}

 

155. Min Stack[Medium]

思路:要求设计一个最小栈,不仅要能进行原有栈操作的基础上,还要能常数复杂度内求出当前栈的最小值。


/**
 * Author Owen_Q
 *
 * a stack that supports push, pop, top, and retrieving the minimum element in constant time.
 * <p>
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 *
 */
public interface MinStack {

    /**
     * pushes the element val onto the stack.
     *
     * @param val the element to be pushed
     */
    void push(int val);

    /**
     * removes the element on the top of the stack.
     */
    public void pop();

    /**
     * gets the top element of the stack.
     *
     * @return the top element of the stack
     */
    public int top();

    /**
     * retrieves the minimum element in the stack.
     *
     * @return the minimum element in the stack
     */
    public int getMin();
}

其实考虑动态变更的数据结构实时求最小值,首先想到的就是优先队列或者堆。但这题需要常数复杂度,那么多思考一下。由于栈的特点是先进后出,即栈底元素一定比上方元素存在的时间长。换句话说,如果某个元素在入栈的那一刻没有成为最小值,那么其将永远没有机会再成为最小值了。因为其下方将永远存在比其小的元素。那么其实我们只需要一个辅助栈,来记录一下当前栈内最小元素,和原栈元素同步更新。当入栈元素比栈内最小元素小时,则更新栈内最小元素,否则栈内最小元素维持原值。

/*
Author Owen_Q
*/
public class MinStackDeque implements MinStack {

    Deque<Integer> stack;
    Deque<Integer> minStack;

    public MinStackDeque() {
        stack = new ArrayDeque<>();
        minStack = new ArrayDeque<>();
    }

    @Override
    public void push(int val) {
        stack.push(val);
        if (minStack.isEmpty() || val <= minStack.peek()) {
            minStack.push(val);
        } else {
            minStack.push(minStack.peek());
        }
    }

    @Override
    public void pop() {
        if (stack.isEmpty()) {
            throw new RuntimeException("stack is empty");
        }
        stack.pop();
        minStack.pop();
    }

    @Override
    public int top() {
        if (stack.isEmpty()) {
            throw new RuntimeException("stack is empty");
        }
        return stack.peek();
    }

    @Override
    public int getMin() {
        if (minStack.isEmpty()) {
            throw new RuntimeException("stack is empty");
        }
        return minStack.peek();
    }
}


/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

多想一下:用链表模拟栈

想着优化一下,但是由于数组的大小未知,因此不能直接手撕,想到可以尝试手动模拟链表来实现栈。恰巧,不难发现,其实上面使用的两个栈完全是一模一样的,因此完全可以把栈最小值当成链表节点的一部分,说干就干

/*
Author Owen_Q
*/
public class MinStackNodes implements MinStack {

    private static class StackNode {
        int val;
        int minVal;
        StackNode next;

        public StackNode(int val, int minVal, StackNode next) {
            this.val = val;
            this.minVal = minVal;
            this.next = next;
        }
    }

    StackNode stack;

    public MinStackNodes() {
        stack = null;
    }

    @Override
    public void push(int val) {
        if (stack == null) {
            stack = new StackNode(val, val, null);
        } else {
            stack = new StackNode(val, Math.min(val, stack.minVal), stack);
        }
    }

    @Override
    public void pop() {
        if (stack == null) {
            throw new RuntimeException("stack is empty");
        }
        stack = stack.next;
    }

    @Override
    public int top() {
        if (stack == null) {
            throw new RuntimeException("stack is empty");
        }
        return stack.val;
    }

    @Override
    public int getMin() {
        if (stack == null) {
            throw new RuntimeException("stack is empty");
        }
        return stack.minVal;
    }
}


/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(val);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

效果拔群

20. Valid Parentheses[Easy]

思路:括号验证,括号问题其实很容易想到栈,直接用栈模拟一下即可。最后特判一下如果串长是奇数那么直接返回失败即可

/*
Author Owen_Q
*/
public class ParenthesesValidator {

    public static boolean isValid(String s) {
        int len = s.length();
        char[] stack = new char[len];
        int top = 0;
        if ((len & 1) == 1) {
            return false;
        }
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if (c == '(') {
                stack[top++] = ')';
            } else if (c == '[') {
                stack[top++] = ']';
            } else if (c == '{') {
                stack[top++] = '}';
            } else {
                if (top == 0 || stack[--top] != c) {
                    return false;
                }
            }
        }
        return top == 0;
    }
}

394. Decode String[Medium]

思路:一般存在递归嵌套的结构都首先想到栈,这里栈需要维护两个值,一个是前序字母序列,还有一个就是需要重复的次数。然后用一个变量实时维护当前字符串,另一个变量将重复次数从字符串转换为数字。于是操作就变得很清晰了,如果遇到字母或者数字,就维护两个特定的变量。每次遇到左括号时,将维护的字符串和重复次数加入栈内,并将两个变量清空。每次遇到右括号时,则将栈顶元素出栈,获得前序字母序列和重复次数。于是将当前维护的字符串变量重复特定次数后加上前序字母序列,组成新的字符串再维护到当前变量中。

/*
Author Owen_Q
*/
public class StringDecoder {

    private static class DecoderNode {
        int count;
        StringBuffer str;
        DecoderNode next;

        public DecoderNode(int count, StringBuffer str, DecoderNode next) {
            this.count = count;
            this.str = str;
            this.next = next;
        }
    }

    public static String decodeString(String s) {
        int len = s.length();
        StringBuffer st = new StringBuffer();
        int cnt = 0;
        DecoderNode stack = null;
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if (c <= '9' && c >= '0') {
                cnt = cnt * 10 + c - '0';
            } else if (c == '[') {
                stack = new DecoderNode(cnt, st, stack);
                st = new StringBuffer();
                cnt = 0;
            } else if (c == ']') {
                if (stack == null) {
                    throw new RuntimeException("stack is empty");
                }
                int stackCount = stack.count;
                for (int j = 0; j < stackCount; j++) {
                    stack.str.append(st);
                }
                st = stack.str;
                stack = stack.next;
            } else {
                st.append(c);
            }
        }
        return st.toString();
    }
}

用链表维护栈是真的好用,效率直接拉满

多想一下:转栈为DFS

有句话叫dfs问题都可以转化为栈问题,那么这题其实就是典型的dfs转化而来的栈,那么复原用dfs实现一下试试

/*
Author Owen_Q
*/
public class StringDecoder {

    public static String decodeStringWithDfs(String s) {
        return dfs(s, 0).getValue().toString();
    }

    private static Pair<Integer, StringBuffer> dfs(String s, int pos) {
        StringBuffer st = new StringBuffer();
        int cnt = 0;
        int len = s.length();
        while (pos < len) {
            char c = s.charAt(pos);
            if (c <= '9' && c >= '0') {
                cnt = cnt * 10 + c - '0';
            } else if (c == '[') {
                Pair<Integer, StringBuffer> pair = dfs(s, pos + 1);
                StringBuffer stackStr = pair.getValue();
                for (int j = 0; j < cnt; j++) {
                    st.append(stackStr);
                }
                cnt = 0;
                pos = pair.getKey();
            } else if (c == ']') {
                return new Pair<>(pos, st);
            } else {
                st.append(c);
            }
            pos++;
        }
        return new Pair<>(cnt, st);
    }
}

 84. Largest Rectangle in Histogram[Hard]

思路:求直方图的最大矩形区域,这个和当年阿里面试题简直有异曲同工之妙。最大01子矩阵问题(单调栈优化)_01最大子矩阵-CSDN博客

在原有单调栈求下一个最小值(最大值的变种)基础上,要分别求元素的前后方的最小值。

再回忆一下单调栈。针对每个位置,会将栈中所有更大的数全都出站,那么此时栈定元素就是下一个更小的数。接下来要求上一个更小的数。我们再继续思考出入栈的过程,刚刚说到,此时将当前数入栈,那么当前数再次出栈的时候栈顶元素一定还是下一个更小的数。而恰巧,当前数出栈的时候,就说明了上一个更小的数出现了,因为只有更小的数才能驱使栈内元素出栈。总结而言,当栈内元素出栈时,当前元素是出栈元素的上一个更小元素,出栈后的栈顶元素是出栈元素的下一个更小的元素。

再总结一下,针对单调栈,如果只需要求下一个更小元素,可以在枚举位置处获取到。但如果想知道上一个更小元素,则需要等到元素出栈后才能获取到

/*
Author Owen_Q
*/
public class LargestHistogramRectangle {

    public static int largestRectangleArea(int[] heights) {
        int len = heights.length;
        int[] stack = new int[len];
        int top = -1;
        int result = 0;
        for (int i = len - 1; i >= 0; i--) {
            while (top >= 0 && heights[i] <= heights[stack[top]]) {
                int now = heights[stack[top]];
                top--;
                int width = top == -1 ? len - i - 1 : stack[top] - i - 1;
                result = Math.max(result, width * now);
            }
            stack[++top] = i;
        }
        while (top >= 0) {
            int now = heights[stack[top]];
            top--;
            int width = top == -1 ? len : stack[top];
            result = Math.max(result, width * now);
        }
        return result;
    }
}

完美

完结撒花

 


网站公告

今日签到

点亮在社区的每一天
去签到