LeetCode入門指南 之 棧和佇列

WINLSR 發表於 2021-08-30
LeetCode

155. 最小棧

設計一個支援 pushpoptop 操作,並能在常數時間內檢索到最小元素的棧。

  • push(x) —— 將元素 x 推入棧中。
  • pop() —— 刪除棧頂的元素。
  • top() —— 獲取棧頂元素。
  • getMin() —— 檢索棧中的最小元素。
class MinStack {

    /** initialize your data structure here. */
    private Deque<integer> stack;
    // 額外用一個棧儲存最小值
    private Deque<integer> minstack;
    public MinStack() {
        stack = new LinkedList<>();
        minstack = new LinkedList<>();
    }
    
    public void push(int val) {
        stack.push(val);
        if (minstack.isEmpty() || val < minstack.peek()) {
            minstack.push(val);
        } else {
            minstack.push(minstack.peek());
        }
    }
    
    public void pop() {
        stack.pop();
        minstack.pop();
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        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();
 */

150. 逆波蘭表示式求值

根據 逆波蘭表示法,求表示式的值。

有效的算符包括 +-*/ 。每個運算物件可以是整數,也可以是另一個逆波蘭表示式。

說明:

  • 整數除法只保留整數部分。
  • 給定逆波蘭表示式總是有效的。換句話說,表示式總會得出有效數值且不存在除數為 0 的情況。
class Solution {
    /**
     *  思路:遇見數字就入棧,遇見操作符就彈出兩個運算元進行相應操作並將結果入棧。
     *        注意:加法和乘法不需要關注兩個數的先後順序,減法和除法中,後出棧的數在操作符之前
     */
    private Deque<integer> stack = new LinkedList<>();

    public int evalRPN(String[] tokens) {
        String temp;
        for (int i = 0; i < tokens.length; i++) {
            temp = tokens[i];

            switch (temp) {
            case "+":
                stack.push(stack.pop() + stack.pop());
                break;
            case "-":
                int j = stack.pop();
                stack.push(stack.pop() - j);
                break;
            case "*":
                stack.push(stack.pop() * stack.pop());
                break;
            case "/":
                int k = stack.pop();
                stack.push(stack.pop() / k);
                break;
            default:
                stack.push(Integer.parseInt(temp));
            }
        }

        return stack.peekFirst();
    }
}

394. 字串解碼

給定一個經過編碼的字串,返回它解碼後的字串。

編碼規則為: k[encoded_string],表示其中方括號內部的 encoded_string 正好重複 k 次。注意 k 保證為正整數。

你可以認為輸入字串總是有效的;輸入字串中沒有額外的空格,且輸入的方括號總是符合格式要求的。

此外,你可以認為原始資料不包含數字,所有的數字只表示重複的次數 k ,例如不會出現像 3a2[4] 的輸入。

思路:棧,思路簡單,關鍵在於字串拼接順序的細節問題。

class Solution {
    public String decodeString(String s) {
        Deque<string> stack = new LinkedList<>();
        
        for (int i = 0; i < s.length(); i++) {
            String str = s.substring(i, i + 1);

            if (str.equals("]")) {
                //拼接 [] 之間的字元,這裡得到的是逆序,不用反轉
                StringBuilder strSB = new StringBuilder();
                while (!stack.peek().equals("[")) {
                    strSB.append(stack.pop());
                }

                //彈出 [
                stack.pop();

                //拼接 [ 之前的重複次數
                StringBuilder reTimesSB = new StringBuilder();
                while (!stack.isEmpty() && isDigit(stack.peek())) {
                    reTimesSB.append(stack.pop());
                }

                //根據重複次數拼接字串,反轉後轉為整型
                int reTimes = Integer.parseInt(reTimesSB.reverse().toString());

                StringBuilder sb = new StringBuilder();
                while (reTimes > 0) {
                    sb.append(strSB);
                    reTimes--;
                }

                //新字串入棧
                stack.push(sb.toString());
            } else {
                stack.push(str);
            }
        }

        StringBuilder res = new StringBuilder();
        while (!stack.isEmpty()) {
            res.append(stack.pop());
        }

        //由於之前的字元拼接都是逆序的,反轉後再返回
        return res.reverse().toString();
    }

    //首字元是否為數字
    private boolean isDigit(String str) {
        char ch = str.charAt(0);

        return ch >= '0' && ch <= '9';
    }
}

133. 克隆圖

給你無向 連通 圖中一個節點的引用,請你返回該圖的 深拷貝(克隆)。

圖中的每個節點都包含它的值 valint) 和其鄰居的列表(list[Node])。

class Node {
    public int val;
    public List<node> neighbors;
}
class Solution {
    private HashMap<node, node=""> visited = new HashMap<>();
    /**
     *  函式定義:
     *          克隆當前結點開始的圖並返回當前結點
     */
    public Node cloneGraph(Node node) {
        if (node == null) {
            return node;
        }

        //base case 已經克隆過的結點就直接返回
        if (visited.containsKey(node)) {
            return visited.get(node);
        }

        /**
         * 對於node來說,克隆整個圖就是先克隆自己,再克隆所有以其子結點開始的圖,然後返回
         * 根據函式定義易寫出如下程式碼
         */
        Node cloneNode = new Node (node.val, new ArrayList<>());
        visited.put(node, cloneNode);

        for (Node tempNode : node.neighbors) {
            cloneNode.neighbors.add(cloneGraph(tempNode));
        }

        return cloneNode;
    }
}
class Solution {
    HashMap<node, node=""> map = new HashMap<>();

    public Node copyRandomList(Node head) {
        if (head == null) {
            return head;
        }

        //base case 
        if (map.containsKey(head)) {
            return map.get(head);
        }
        
        /**
         *  對當前結點來說,克隆整個連結串列等於先克隆自己,再克隆next和random子結點開始的連結串列
         */
        Node cloneNode = new Node(head.val);
        map.put(head, cloneNode);

        cloneNode.next = copyRandomList(head.next);
        cloneNode.random = copyRandomList(head.random);

        return cloneNode;
    }
}

200. 島嶼數量

給你一個由 '1'(陸地)和 '0'(水)組成的的二維網格,請你計算網格中島嶼的數量。

島嶼總是被水包圍,並且每座島嶼只能由水平方向和/或豎直方向上相鄰的陸地連線形成。

此外,你可以假設該網格的四條邊均被水包圍。

思路:DFS

  • 對於二維矩陣中每個結點來說,他有上、下、左、右四個鄰居,因此可以將每個島嶼都看成一個圖。
  • 從任意一個陸地進入開始遍歷,遍歷完 1 次就代表發現了 1 個島嶼。 注:圖不像樹那樣是有向的,遍歷可能會訪問重複結點,一般需要用額外結構表示結點是否已經被訪問過。此題可以直接在矩陣上將 1 修改為 2 表示結點已經訪問過。

在原矩陣中標記是否訪問過:

class Solution {
    public int numIslands(char[][] grid) {
        int count = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == '1') {
                    dfs(grid, i, j);
                    count++;
                }
            }
        }
        
        return count;
    }

    private void dfs(char[][] grid, int row, int col) {
        //base case,越界
        if (!inArea(grid, row, col)) {
            return;
        }
        //base case,是水 或 已經訪問過的陸地
        if (grid[row][col] != '1') {
            return;
        }

        //標記為已訪問
        grid[row][col] = '2';

        dfs(grid, row + 1, col);
        dfs(grid, row - 1, col);
        dfs(grid, row, col + 1);
        dfs(grid, row, col - 1);
    }

    private boolean inArea(char[][] grid, int row, int col) {
        return row >= 0 && row < grid.length &&
            col >= 0 && col < grid[0].length;
    }
}

使用額外空間標記是否已經訪問過:

class Solution {

    // 標記是否訪問過
    private boolean[][] visited;

    public int numIslands(char[][] grid) {
        int row = grid.length;
        int col = grid[0].length;
        visited = new boolean[row][col];

        int cnt = 0;

        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                if (grid[i][j] == '1' && !visited[i][j]) {
                    dfs(grid, i, j);
                    cnt++;
                }
            }
        }

        return cnt;
    }

    

    private void dfs (char[][] grid, int i, int j) {
        if (!inArea(grid, i, j)) {
            return;
        }

        if (grid[i][j] != '1' || visited[i][j]) {
            return;
        }

        visited[i][j] = true;

        dfs(grid, i + 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i - 1, j);
        dfs(grid, i, j - 1);
    }

    private boolean inArea(char[][] grid, int row, int col) {
        return row >= 0 && row < grid.length &&
            col >= 0 && col < grid[0].length;
    }
}

推薦閱讀:島嶼類問題的通用解法、DFS 遍歷框架最大人工島

84. 柱狀圖中最大的矩形

給定 n 個非負整數,用來表示柱狀圖中各個柱子的高度。每個柱子彼此相鄰,且寬度為 1 。

求在該柱狀圖中,能夠勾勒出來的矩形的最大面積。

img

以上是柱狀圖的示例,其中每個柱子的寬度為 1,給定的高度為 [2,1,5,6,2,3]

img

圖中陰影部分為所能勾勒出的最大矩形面積,其面積為 10 個單位。

思路:單調遞增棧,依次遍歷陣列,大於等於棧頂元素直接入棧,小於則彈棧並計算一次面積。

class Solution {
    public int largestRectangleArea(int[] heights) {
        int len = heights.length;
        int[] newHeight = new int[len + 2];

        //將 heights 複製到 newHeight,同時將首尾各填充一個 -1
        newHeight[0] = -1;
        newHeight[len - 1] = -1;
        for (int i = 1; i <= len; i++) {
            newHeight[i] = heights[i - 1];
        }

        int maxArea = 0;
        Deque<integer> stack = new LinkedList<>();
        for (int i = 0; i < newHeight.length; i++) {
            //出棧
            while (!stack.isEmpty() && newHeight[stack.peek()] > newHeight[i]) {
                int high = newHeight[stack.pop()];
                int width = i - stack.peek() - 1;	// 下標 [stack.peek() + 1, i - 1] 對應元素都 大於等於 high
                int area = high * width;
                maxArea = Math.max(area, maxArea);
            }

            //入棧
            stack.push(i);
        }

        return maxArea;
    }
}

推薦閱讀:詳解單調棧,🤷‍♀️必須秒懂!

42.接雨水

給定 n 個非負整數表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之後能接多少雨水。

示例 1:

img

輸入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
輸出:6
解釋:上面是由陣列 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。 

示例 2:

輸入:height = [4,2,0,3,2,5]
輸出:9

推薦一種不使用單調棧而是使用備忘錄的解法:

思路:在左邊找大於等於當前高度的最大值,右邊也找大於等於當前高度的最大值,兩者取最小值再減去當前高度即為當前下標所能接的雨水量。

class Solution {
    public int trap(int[] height) {
        if (height == null || height.length <= 2) {
            return 0;
        }
        int len = height.length;

        //分別記錄元素左邊和右邊的最大值
        int[] leftMax = new int[len];
        int[] rightMax = new int[len];

        //最左邊元素左邊的最大值
        leftMax[0] = height[0];
        //最右邊元素右邊的最大值
        rightMax[len - 1] = height[len - 1];

        for (int i = 1; i < len; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], height[i]);
        }

        for (int i = len - 2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i + 1], height[i]);
        }

        int area = 0;
        for (int i = 1; i < len - 1; i++) {
            area += Math.min(leftMax[i], rightMax[i]) - height[i];
        }

        return area;
    }
}

佇列

232. 用棧實現佇列

請你僅使用兩個棧實現先入先出佇列。佇列應當支援一般佇列支援的所有操作(pushpoppeekempty):

實現 MyQueue 類:

  • void push(int x) 將元素 x 推到佇列的末尾
  • int pop() 從佇列的開頭移除並返回元素
  • int peek() 返回佇列開頭的元素
  • boolean empty() 如果佇列為空,返回 true ;否則,返回 false
class MyQueue {
    private Deque<integer> instack;
    private Deque<integer> outstack;

    /** Initialize your data structure here. */
    public MyQueue() {
        instack = new LinkedList<>();
        outstack = new LinkedList<>();
    }
    
    /** Push element x to the back of queue. */
    public void push(int x) {
        instack.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    public int pop() {
        if (outstack.isEmpty()) {
            while (!instack.isEmpty()) {
                outstack.push(instack.pop());
            }
        }

        return outstack.pop();
    }
    
    /** Get the front element. */
    public int peek() {
        if (outstack.isEmpty()) {
            while (!instack.isEmpty()) {
                outstack.push(instack.pop());
            }
        }

        return outstack.peek();
    }
    
    /** Returns whether the queue is empty. */
    public boolean empty() {
        return instack.isEmpty() && outstack.isEmpty();
    }
}

542. 01 矩陣

給定一個由 0 和 1 組成的矩陣,找出每個元素到最近的 0 的距離。

兩個相鄰元素間的距離為 1 。

BFS:

class Solution {
    /**
     * 思路:
     *      同樣將矩陣看成圖的結構,每個位置有上下左右四個鄰居,所有的0都作為圖的源點開始bfs
     */
    
    public int[][] updateMatrix(int[][] matrix) {
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                //0作為源點全部加入佇列
                if (matrix[i][j] == 0) {
                    queue.offer(new Integer[] {i, j});
                //1置為-1表示未訪問過的點
                } else if (matrix[i][j] == 1) {
                    matrix[i][j] = -1;
                }
            }
        }

        //將隊首元素出隊並訪問其上下左右四個未訪問的鄰居,然後加入隊尾
        int[][] directions = new int[][] {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
        while (!queue.isEmpty()) {
            Integer[] point = queue.poll();
            int x = point[0];
            int y = point[1];

            for (int i = 0; i < 4; i++) {
                int newX = x + directions[i][0];
                int newY = y + directions[i][1];

                if (newX >= 0 && newX < matrix.length && 
                    newY >= 0 && newY < matrix[0].length && 
                    matrix[newX][newY] == -1) {

                        matrix[newX][newY] = matrix[x][y] + 1;
                        queue.offer(new Integer[] {newX, newY});
                }
            }
        }

        return matrix;
    }

    private Deque<integer[]> queue = new LinkedList<>();
}

動態規劃(學習了動態規劃再回過頭來看):

當前點到最近0的距離取決於其四個鄰居到最近0的距離的最小值加1,應嘗試用動態規劃來解決。

class Solution {

    public int[][] updateMatrix(int[][] matrix) {
        //dp元素表示當前下標的點到其最近0的距離
        int rows = matrix.length;
        int cols = matrix[0].length;
        int[][] dp = new int[rows][cols];
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (matrix[i][j] == 0) {
                    dp[i][j] = 0;
                } else if (matrix[i][j] == 1) {
                    //求最小值先初始化為足夠大,初始化為Integer.MAX_VALUE會越界
                    dp[i][j] = Integer.MAX_VALUE - 1;
                }
            }
        }

        /**
         * 一個點的dp值是由其上下左右四個狀態來決定,無法從一個方向開始遞推!
         * 最簡單的方式是從四個方向分別遞推一次
         */

        //上到下
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (i - 1 >= 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + 1);
                }
            }
        }

        //左到右
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (j - 1 >= 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + 1);
                }
            }
        }

        //下到上
        for (int i = rows - 1; i >= 0; i--) {
            for (int j = cols - 1; j >= 0; j--) {
                if (i + 1 < rows) {
                    dp[i][j] = Math.min(dp[i][j], dp[i + 1][j] + 1);
                }
            }
        }

        //右到左
        for (int i = rows - 1; i >= 0; i--) {
            for (int j = cols - 1; j >= 0; j--) {
                if (j + 1 < cols) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j + 1] + 1);
                }
            }
        }
        
        return dp;
    }
}

仔細觀察發現其實可以將前兩次遞推合併,後兩次遞推也可以合併。

class Solution {

    public int[][] updateMatrix(int[][] matrix) {
        //dp元素表示當前下標的點到其最近0的距離
        int rows = matrix.length;
        int cols = matrix[0].length;
        int[][] dp = new int[rows][cols];
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (matrix[i][j] == 0) {
                    dp[i][j] = 0;
                } else if (matrix[i][j] == 1) {
                    //求最小值先初始化為足夠大,初始化為Integer.MAX_VALUE會越界
                    dp[i][j] = Integer.MAX_VALUE - 1;
                }
            }
        }

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (i - 1 >= 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + 1);
                }
                if (j - 1 >= 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + 1);
                }
            }
        }

        for (int i = rows - 1; i >= 0; i--) {
            for (int j = cols - 1; j >= 0; j--) {
                if (i + 1 < rows) {
                    dp[i][j] = Math.min(dp[i][j], dp[i + 1][j] + 1);
                }
                if (j + 1 < cols) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j + 1] + 1);
                }
            }
        }
        
        return dp;
    }
}

推薦閱讀:2種BFS,詳解DP, 🤷‍♀️必須秒懂!

225. 用佇列實現棧

請你僅使用兩個佇列實現一個後入先出(LIFO)的棧,並支援普通佇列的全部四種操作(pushtoppopempty)。

實現 MyStack 類:

  • void push(int x) 將元素 x 壓入棧頂。
  • int pop() 移除並返回棧頂元素。
  • int top() 返回棧頂元素。
  • boolean empty() 如果棧是空的,返回 true ;否則,返回 false
class MyStack {
    private Deque<integer> inQueue;
    private Deque<integer> outQueue;

    /** Initialize your data structure here. */
    public MyStack() {
        inQueue = new LinkedList<>();
        outQueue = new LinkedList<>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        inQueue.offer(x);
        while (!outQueue.isEmpty()) {
            inQueue.offer(outQueue.poll());
        }
        Deque<integer> temp = inQueue;
        inQueue = outQueue;
        outQueue = temp;
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return outQueue.poll();
    }
    
    /** Get the top element. */
    public int top() {
        return outQueue.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return outQueue.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

用一個佇列實現棧:

class MyStack {
    private Deque<integer> queue;

    /** Initialize your data structure here. */
    public MyStack() {
        queue = new LinkedList<>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        int size = queue.size();
        //先將新元素入隊
        queue.offer(x);
        //將其前面的元素依次出隊並重新入隊
        for (int i = 0; i < size; i++) {
            queue.offer(queue.poll());
        }
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return queue.poll();
    }
    
    /** Get the top element. */
    public int top() {
        return queue.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue.isEmpty();
    }
}

</integer[]></node,></node,>