155. 最小栈

image.png

  • way1:思路:使用两个栈,栈1存放所有push进的元素,栈2存放当前栈1中最小的元素。当push元素的时候,和最小元素比较一下,如果大于等于当前最小元素则将该元素也push到栈2中,当pop元素时,也和当前最小元素比较一下,如果和当前最小元素相等,则也将栈2元素pop出去,这样就能时刻保证以常数时间复杂度获取到最小的元素。

    1. class MinStack {
    2. private Stack<Integer> stack;
    3. private Stack<Integer> minStack;
    4. /** initialize your data structure here. */
    5. public MinStack() {
    6. stack = new Stack();
    7. minStack = new Stack();
    8. }
    9. public void push(int x) {
    10. stack.push(x);
    11. if (minStack.empty() || minStack.peek() >= x) {
    12. minStack.push(x);
    13. }
    14. }
    15. public void pop() {
    16. if (!stack.empty()){
    17. int pop = stack.pop();
    18. if (minStack.peek() == pop) {
    19. minStack.pop();
    20. }
    21. }
    22. }
    23. public int top() {
    24. return stack.peek();
    25. }
    26. public int getMin() {
    27. return minStack.peek();
    28. }
    29. }
  • way2:其实可以创造一个Node节点,包含当前值和最小值。

    1. class MinStack {
    2. private Node head;
    3. /** initialize your data structure here. */
    4. public MinStack() {
    5. head = new Node(-1, Integer.MAX_VALUE, null);
    6. }
    7. public void push(int x) {
    8. head = new Node(x,Math.min(x, head.minVal),head);
    9. }
    10. public void pop() {
    11. head = head.next;
    12. }
    13. public int top() {
    14. return head.val;
    15. }
    16. public int getMin() {
    17. return head.minVal;
    18. }
    19. class Node {
    20. private int val;
    21. private int minVal;
    22. private Node next;
    23. Node (int val, int minVal, Node next) {
    24. this.val = val;
    25. this.minVal = minVal;
    26. this.next = next;
    27. }
    28. }
    29. }

    239. 滑动窗口最大值(重点)

    image.png

    方法1:双端队列法

    思路:使用一个双端队列,保证队头的元素是当前滑动窗口的最大值,队列里的元素依次减小。
    注意点:

  • 当窗口往后移动一格时,将队列里面的元素从尾到头和该元素比大小,小的出队,然后放入当前元素

  • 当队头元素不在窗口时,对头出队列。

    1. class Solution {
    2. public int[] maxSlidingWindow(int[] nums, int k) {
    3. if (nums == null || nums.length < 2) return nums;
    4. if (k == 1) return nums;
    5. int[] res = new int[nums.length - k + 1];
    6. LinkedList<Integer> queue = new LinkedList();
    7. for (int i = 0; i < nums.length; i++) {
    8. while (!queue.isEmpty() && nums[i] > nums[queue.peekLast()]) {
    9. queue.pollLast();
    10. }
    11. queue.addLast(i);
    12. if (i - k + 1 > queue.peekFirst()) {// i-k+1窗口左指针
    13. queue.pollFirst();
    14. }
    15. if (i - k + 1 >= 0) {
    16. res[i - k + 1] = nums[queue.peekFirst()];
    17. }
    18. }
    19. return res;
    20. }
    21. }

    方法2:朴素法

  • 记录窗口最大值的索引,每次窗口往右移动1位时,首先比较最大值的索引和窗口的左边界大小,即检查最大值有没有过期,如果过期,则需要在窗口里重新找到最大值,如果没有过期就好办了,只需要比较一下新增的一位是否比当前最大值还大,这样就能快速得到当前窗口的最大值。

    这个方法在有些用例下会跑到很慢,比如严格递减的用例,每次窗口右移都会导致最大值过期,性能降为O(n * k);而对于严格递增的用例,效率则最高。综上还是方法1合适

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k < 2) return nums;
        int maxIdx = 0;
        int[] res = new int[nums.length - k + 1];
        for (int i = 1; i < k; i++) {
            if (nums[i] >= nums[maxIdx]) maxIdx = i;
        }
        for (int right = k - 1; right < nums.length; right++) {
            int left = right - k + 1;
            if (left > maxIdx) {
                maxIdx = left;
                for (int i = left; i <= right; i++) {
                    if (nums[i] >= nums[maxIdx]) maxIdx = i;
                }
            }else if (nums[right] >= nums[maxIdx]) {
                maxIdx = right;
            }
            res[left] = nums[maxIdx];
        }
        return res;
    }
}

654. 最大二叉树

image.png

方法1:递归法

这道题第一思路是用递归法去解决。

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        if (nums == null || nums.length == 0) return null;
        return constructMaximumBinaryTree(nums, 0, nums.length - 1);
    }
    private TreeNode constructMaximumBinaryTree(int[] nums, int l, int r) {
        if (l > r){
            return null;
        }
        if (l == r) {
            return new TreeNode(nums[l]);
        }

        int i = findMaxIndex(nums, l, r);
        TreeNode root = new TreeNode(nums[i]);
        root.left = constructMaximumBinaryTree(nums, l, i - 1);
        root.right = constructMaximumBinaryTree(nums, i + 1, r);
        return root;
    }
    private int findMaxIndex(int[] nums,int l, int r) {
        int maxIndex = l, max = nums[l];
        for (int i = l + 1; i <= r; i++) {
            if (max < nums[i]) {
                max = nums[i];
                maxIndex = i;
            }
        }
        return maxIndex;
    }
}

方法2:单调栈法

分析题目发现,每个结点的父节点其实是左侧第一个大于该节点大的值和右侧第一个大于该节点的值中的最小值,这跟接雨水那道题求洼点的积水一样一样的,单调栈走起啊,维护一个栈,里面的元素只能存单调减小的,一旦遇到一个比栈顶元素大的,说明当前栈顶元素的左右最大值都已找到,然后弹出该元素,后面就可以干活了(找到较小的那一个,然后将其指向栈顶元素),就这样循环下去。。。

class Solution {
    public TreeNode constructMaximumBinaryTree(int[] nums) {
        if (nums == null || nums.length == 0) return null;
        Stack<TreeNode> stack = new Stack();
        for (int i = 0; i < nums.length; i++) {
            TreeNode newNode = new TreeNode(nums[i]);
            while (!stack.isEmpty() && nums[i] > stack.peek().val){
                TreeNode popNode = stack.pop();
                if (stack.isEmpty() || stack.peek().val > nums[i]) {
                    newNode.left = popNode;
                } else {
                    stack.peek().right = popNode;
                }
            }
            stack.push(newNode);
        }
        TreeNode head = stack.pop();
        while (!stack.isEmpty()) {
            stack.peek().right = head;
            head = stack.pop();
        }
        return head;
    }
}

739. 每日温度

image.png
思路:

方法1:单调栈法

栈里维护温度递减的索引,一旦遇到一个比当前栈底大的元素,说明遇到了升温,这个弹出元素,计算索引的差值即可,循环下去,最后留在栈里的元素,都说明这些元素的右侧没有比它们大的了。

class Solution {
    public int[] dailyTemperatures(int[] T) {
        if (T == null || T.length == 0) return T;
        int[] res = new int[T.length];
        Stack<Integer> stack = new Stack();
        for (int i = 0; i < T.length; i++) {
            while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
                int curIdx = stack.pop();
                res[curIdx] = i - curIdx;
            }
            stack.push(i);
        }
        return res;
    }
}

方法2:动态规划法

从后往前计算,因为最后一位必然是0,相当于初始条件,如果T[i+1]>T[i] 则res[i]=1,否则跳到比T[i+1]大的元素,即T[i+1+res[i+1]],再跟T[i]比较大小,详细过程如下:
image.png

class Solution {
    public int[] dailyTemperatures(int[] T) {
        if (T == null || T.length == 0) return new int[0];
        int[] res = new int[T.length];
        for (int i = T.length - 2; i >= 0; i--) {
            int j = i + 1;
            while (true) {
                if (T[j] > T[i]) {
                    res[i] = j - i;
                    break;
                } else if (res[j] == 0) {
                    break;
                } else {
                    j = res[j] + j;
                }
            }
        }
        return res;
    }
}

42. 接雨水

image.png

思路:维护一个单调递减栈,当小于栈顶元素时,直接入栈,如果大于栈顶元素,说明栈顶元素所在位置就是洼地,可以先计算洼地的积水。

class Solution {
    public int trap(int[] height) {
        if (height == null || height.length < 3) return 0;
        Stack<Integer> stack = new Stack();
        int sum = 0;
        for (int i = 0; i < height.length; i++) {
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int curIdx = stack.pop();
                if (stack.isEmpty()) break;
                int leftIdx = stack.peek();
                int depth = Math.min(height[i], height[leftIdx]) - height[curIdx];
                int width = i - leftIdx - 1;
                sum += width * depth;
            }
            stack.push(i);
        }
        return sum;
    }
}