栈、递归(深度优先)


二叉树的中序遍历

给定一个二叉树,返回它的中序 遍历。

递归解法
时间复杂度O(n);
空间复杂度最坏情况下O(n);

 public List inorderTraversal(TreeNode root) {
        List vals = new ArrayList<>();
        recursion(vals, root);
        return vals;
    }

    public void recursion(List vals, TreeNode node){

        if(node != null){
            if(node.left != null){
                recursion(vals, node.left);
            }
            vals.add(node.val);
            if(node.right != null){
                recursion(vals, node.right);
            }
        }
    }


1)针对每个current节点,while循环找到最左节点,遍历路径上的所有节点入栈;
2)出栈,输出current节点的值,current节点指向当前节点的右子树;
3)重复1)
时间复杂度O(n);
空间复杂度O(n);

public List inorderTraversal2(TreeNode root) {
        List vals = new ArrayList<>();
        TreeNode current = root;
        Stack stack = new Stack<>();

        while (!stack.isEmpty() || current!=null){
            while (current != null){
                stack.push(current);
                current = current.left;
            }
            current = stack.pop();
            vals.add(current.val);
            current = current.right;
        }

        return vals;
    }

莫里斯遍历

若current没有左子节点
    a. 将current添加到输出
    b. 进入右子树,亦即, current = current.right
否则
    a. 在current的左子树中,令current成为最右侧节点的右子节点
    b. 进入左子树,亦即,current = current.left

举例而言:

      1
    /   \
   2     3
  / \   /
 4   5 6

首先,1 是根节点,所以将 current 初始化为 1。1 有左子节点 2,current 的左子树是

     2
    / \
   4   5

在此左子树中最右侧的节点是 5,于是将 current(1) 作为 5 的右子节点。令 current = cuurent.left (current = 2)。
现在二叉树的形状为:

     2
    / \
   4   5
        \
         1
          \
           3
          /
         6

对于 current(2),其左子节点为4,我们可以继续上述过程

    4
     \
      2
       \
        5
         \
          1
           \
            3
           /
          6

由于 4 没有左子节点,添加 4 为输出,接着依次添加 2, 5, 1 。节点 3 有左子节点 6,故重复以上过程,,令3成为6的右子节点,然后添加6,3。
最终的结果是 [4,2,5,1,6,3]。

 public List < Integer > inorderTraversal(TreeNode root) {
        List < Integer > res = new ArrayList < > ();
        TreeNode curr = root;
        TreeNode pre;
        while (curr != null) {
            if (curr.left == null) {
                res.add(curr.val);
                curr = curr.right; // move to next right node
            } else { // has a left subtree
                pre = curr.left;
                while (pre.right != null) { // find rightmost
                    pre = pre.right;
                }
                pre.right = curr; // put cur after the pre node
                TreeNode temp = curr; // store cur node
                curr = curr.left; // move cur to the top of the new tree
                temp.left = null; // original cur left be null, avoid infinite loops
            }
        }
        return res;
    }


两两交换链表中的节点(https://leetcode-cn.com/problems/swap-nodes-in-pairs/)

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.

递归
递归三要素:

  1. 找终止条件
  2. 找返回值
  3. 单次过程
public ListNode swapPairs(ListNode head) {
        // 终止条件
        if(head == null || head.next == null){
            return head;
        }

        // 返回值
        ListNode ret = head.next;

        // 单次过程
        ListNode temp = ret.next;
        ret.next = head;
        head.next = swapPairs(temp);

        return ret;
    }

    public class ListNode {
        int val;
        ListNode next;

        ListNode(int x) {
            val = x;
        }
    }
}

二叉树的最近公共祖先(https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]


示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。

说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。

递归解法
本题递归的关键在于如何定义递归方法的返回值。我们定义返回值为:当前节点的子树中是否包含p节点或q节点;那么当前节点是否是最近公共祖先即可表示为:

(inLeftSubTree && inRightSubTree) || (inCurrentNode && (inLeftSubTree || inRightSubTree))

inLeftSubTree表示当前节点的左子树是否包含p节点或q节点;
inRightSubTree表示当前节点的右子树是否包含p节点或q节点;
inCurrentNode 表示当前节点是否是p节点或q节点;

    private static TreeNode ans;

    public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null){
            return null;
        }
        recursion(root, p, q);
        return ans;
    }

    // 当前子树是否包含p或q
    public static boolean recursion(TreeNode root, TreeNode p, TreeNode q){
        if(root == null){
            return false;
        }
        boolean inCurrentNode = root.val == p.val || root.val == q.val;
        boolean inLeftSubTree = recursion(root.left, p, q);
        boolean inRightSubTree = recursion(root.right, p, q);

        // 判断当前节点是否是最深公共祖先
        if((inLeftSubTree && inRightSubTree) || (inCurrentNode && (inLeftSubTree || inRightSubTree))){
            ans = root;
        }

        return inCurrentNode || inLeftSubTree || inRightSubTree;
    }

找到两个节点到根节点的路径
另外一种直观的解法就是找到两个节点到根的路径,然后重合的第一个节点就是最近公共祖先。
可以通过HashMap保存每个节点的父节点,从而找到两个节点到根的路径。

public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        Map parent = new HashMap<>();
        dfs(root, parent);

        List pVisited = new ArrayList<>();
        while (p != null){
            pVisited.add(p.val);
            p = parent.get(p.val);
        }

        while (q != null){
            if(pVisited.contains(q.val)){
                return q;
            }
            q = parent.get(q.val);
        }

        return root;
    }

    public static void dfs(TreeNode root, Map parent){
        if(root == null){
            return;
        }
        if(root.left != null){
            parent.put(root.left.val, root);
            dfs(root.left, parent);
        }
        if(root.right != null){
            parent.put(root.right.val, root);
            dfs(root.right, parent);
        }
    }

也可以直接通过递归找到每个节点到根的路径。

public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        if(root == null){
            return null;
        }

        List pNodes = new ArrayList<>();
        pNodes.add(root);
        finish = false;
        getAncList(root, p, pNodes);

        List qNodes = new ArrayList<>();
        qNodes.add(root);
        finish = false;
        getAncList(root, q, qNodes);

        int n = Math.min(pNodes.size(), qNodes.size());
        for(int i=1; i nodes){

        if(root != null && root.val == p.val){
            finish = true;
            return;
        }

        if(root == null){
            return;
        }

        if(!finish && root.left != null){
            nodes.add(root.left);
            getAncList(root.left, p, nodes);
            if(!finish){
                nodes.remove(nodes.size()-1);
            }
        }

        if(!finish && root.right != null){
            nodes.add(root.right);
            getAncList(root.right, p, nodes);
            if(!finish){
                nodes.remove(nodes.size()-1);
            }
        }

        return;
    }

岛屿的最大面积(https://leetcode-cn.com/problems/max-area-of-island/)

给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

示例 1:
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。
示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。

此题既可以用深度优先算法也可以用广度优先算法。这里介绍深度优先算法(dfs)的两种实现,递归解法和非递归解法(栈)。递归解法一般比较巧妙,代码简洁,但是比较难想;非递归解法,容易复合常规思路,代码相对复杂。

对于此类统计最大连接面积的题型,可以使用沉岛思想。
深度优先(递归解法)
每次面积加1之后,将对应的网格置为0,避免重复计算面积,这种思想叫做沉岛思想。

    public static int maxAreaOfIsland(int[][] grid) {
        int m = grid.length;
        if (m == 0) {
            return 0;
        }
        int n = grid[0].length;
        if (n == 0) {
            return 0;
        }
        int maxArea = 0;
        for(int i=0; i=grid.length || j>=grid[0].length || grid[i][j]==0){
            return 0;
        }
        int num = 1;
        grid[i][j] = 0;
        num += dfs(grid, i-1, j);
        num += dfs(grid, i+1, j);
        num += dfs(grid, i, j-1);
        num += dfs(grid, i, j+1);
        return num;
    }

深度优先(栈解法)
深度优先可以使用栈实现,碰到值为1的网格,入栈,并且其周围的值为1的网格入栈;

public static int maxAreaOfIsland(int[][] grid) {
        int m = grid.length;
        if (m == 0) {
            return 0;
        }
        int n = grid[0].length;
        if (n == 0) {
            return 0;
        }
        int maxArea = 0;
        Stack stack = new Stack<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    int area = 0;
                    stack.push(new int[]{i,j});
                    while (!stack.isEmpty()){
                        int[] pos = stack.pop();
                        int r = pos[0];
                        int c = pos[1];
                        if(grid[r][c] == 1){
                            area++;
                            grid[r][c] = 0;
                            if (r - 1 >= 0 && grid[r-1][c] == 1) {
                                stack.push(new int[]{r - 1, c});
                            }
                            if (r + 1 < m && grid[r+1][c] == 1) {
                                stack.push(new int[]{r + 1, c});
                            }
                            if (c - 1 >= 0 && grid[r][c-1] == 1) {
                                stack.push(new int[]{r, c - 1});
                            }
                            if (c + 1 < n && grid[r][c+1] == 1) {
                                stack.push(new int[]{r, c + 1});
                            }
                        }
                    }
                    maxArea = Math.max(maxArea, area);
                }
            }
        }
        return maxArea;
    }

岛屿数量(https://leetcode-cn.com/problems/number-of-islands/)


深度优先:沉岛法
我们可以将二维网格看成一个无向图,竖直或水平相邻的 1 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 11都会被重新标记为 0。
最终岛屿的数量就是我们进行深度优先搜索的次数。
这里我们使用递归方式实现深度优先算法。

 public static int numIslands(char[][] grid) {
        int m = grid.length;
        if (m == 0) {
            return 0;
        }
        int n = grid[0].length;
        if (n == 0) {
            return 0;
        }
        int island = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if(grid[i][j] == '1'){
                    dfs(grid, i, j);
                    island ++;
                }
            }
        }
        return island;
    }

    public static void dfs(char[][] grid, int row, int col) {
        int m = grid.length;
        int n = grid[0].length;

        if (row < 0 || row >= m || col < 0 || col >= n || grid[row][col] == '0') {
            return;
        }

        grid[row][col] = '0';
        dfs(grid, row + 1, col);
        dfs(grid, row - 1, col);
        dfs(grid, row, col + 1);
        dfs(grid, row, col - 1);
    }

此题同样可以使用广度优先算法实现,使用队列,关键思想仍然是沉岛思想。


简化路径



1)考虑到有../回到前一个目录的操作,联想到使用栈
2)使用/作为分隔符,对路径进行分隔,过滤到空字符和.字符。..出栈,否则入栈。

public static String simplifyPath(String path) {
        String[] segs = path.split("/");
        Stack stack = new Stack();
        for(String seg : segs){
            if("".equals(seg.trim()) || ".".equals(seg)){
                continue;
            }
            if("..".equals(seg)){
                if(!stack.isEmpty()){
                    stack.pop();
                }
            }else {
                stack.push(seg);
            }
        }

        if(stack.isEmpty()){
            return "/";
        }

        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()){
            sb.insert(0, "/" + stack.pop());
        }
        return sb.toString();
    }

字符串解码



考虑到有括号的匹配问题,联想到使用栈。
本题中可能出现括号嵌套的情况,比如 2[a2[bc]],这种情况下我们可以先转化成 2[abcbc],在转化成 abcbcabcbc。我们可以把字母、数字和括号看成是独立的 TOKEN,并用栈来维护这些 TOKEN。具体的做法是,遍历编码字符串:
1)如果当前的字符为数位,解析出一个数字(连续的多个数位)并进栈
2)如果当前的字符为字母或者左括号,直接进栈
3)如果当前的字符为右括号,开始出栈,一直到左括号出栈,出栈序列反转后拼接成一个字符串,此时取出栈顶的数字,就是这个字符串应该出现的次数,我们根据这个次数和字符串构造出新的字符串并进栈
4)重复如上操作,最终将栈中的元素按照从栈底到栈顶的顺序拼接起来,就得到了答案。

    public static String decodeString(String s) {
        StringBuilder ans = new StringBuilder();
        Stack stack = new Stack();
        int n = s.length();
        int ptr = 0;
        while (ptr < n) {
            char c = s.charAt(ptr);
            if (isNumber(c)) {
                StringBuilder num = new StringBuilder(String.valueOf(c));
                while (++ptr < n && isNumber(s.charAt(ptr))) {
                    num.append(s.charAt(ptr));
                }
                stack.push(num.toString());
            } else if (c == ']') {
                StringBuilder word = new StringBuilder();
                String pop = stack.pop();
                while (!"[".equals(pop)) {
                    word.insert(0, pop);
                    pop = stack.pop();
                }
                int k = Integer.valueOf(stack.pop());
                StringBuilder decode = new StringBuilder();
                for (int i = 0; i < k; i++) {
                    decode.append(word);
                }
                stack.push(decode.toString());
                ptr++;
            } else {
                stack.push(String.valueOf(c));
                ptr++;
            }
        }
        while (!stack.isEmpty()){
            ans.insert(0, stack.pop());
        }
        return ans.toString();
    }

    public static boolean isNumber(char c) {
        return c >= '0' && c <= '9';
    }

每日温度



1)假设输出数组为ans[n]。ans[i]的值为i后第一个大于T[i]的数的下标决定的,后面的数决定前面的数的答案,则联想到使用栈;
2)输出值为下标的差值,所以栈里面存的应该是数的下标,而非数本身。
3)具体做法如下:

public int[] dailyTemperatures(int[] T) {
        Stack stack = new Stack();
        int n = T.length;
        int[] ans = new int[n];
        stack.push(0);
        for(int i=1; i T[stack.peek()]){
                int index = stack.pop();
                ans[index] = i - index;
            }
            stack.push(i);
        }
        // 剩下的未出栈的元素, 全都为零, 由于数组初始化为零, 所以不需要处理剩下未出栈的元素
//        while (!stack.isEmpty()){
//            int index = stack.pop();
//            ans[index] = 0;
//        }
        return ans;
    }

朋友圈


深度优先搜索

思路如下:

一次深度优先搜索就将一个连通图的所有顶点遍历完成;所以最后深度优先搜索的次数就是连通图的个数,也就是朋友圈的个数。

public static int findCircleNum(int[][] M) {
        int n = M.length;
        if (n == 0) {
            return 0;
        }
        int ans = 0;
        boolean[] visited = new boolean[n];
        for (int i = 0; i < n; i++) {
            if(!visited[i]){
                dfs(M, visited, i);
                ans ++;
            }
        }
        return ans;
    }

    public static void dfs(int[][] M, boolean[] visited, int i){
        for(int j = 0; j < M[i].length; j++){
            if(!visited[j] && M[i][j] == 1){
                visited[j] = true;
                dfs(M, visited, j);
            }
        }
    }

移掉K位数字



1)依序遍历每一个数字,每当后一个数小于前一个数时,应该移除前面一个数,否则不可能得到更小的数。因为相同位数的数字,高位数越小,整体就越小。
2)后面的数决定前一个数的去留,联想到使用栈。

public String removeKdigits(String num, int k) {
        if(k >= num.length()){
            return "0";
        }
        Stack stack = new Stack<>();
        int index = 0;
        while (k > 0 && index < num.length()){
            char c = num.charAt(index++);
            while (!stack.isEmpty() && k > 0 && c < stack.peek()){
                stack.pop();
                k --;
            }
            stack.push(c);
            // 不能以0开头
            if(stack.size() == 1 && c == '0'){
                stack.pop();
            }
        }
        while (k > 0){
            stack.pop();
            k--;
        }
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()){
            sb.insert(0, stack.pop());
        }
        if(index < num.length()){
            sb.append(num.substring(index));
        }
        return "".equals(sb.toString()) ? "0" : sb.toString();
    }

水壶问题


深度优先
此题的关键在于建模。深度优先(栈、递归)和广度优先(队列)均可解决。本题使用深度优先算法,使用栈实现,因为递归可能会超过Java默认递归层数限制,所以能用栈实现就优先用栈。注意使用HashSet保存状态时,要重写equals方法和hashcode方法。

    public static class Water {
        public int x;
        public int y;

        public Water(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            }
            if (o == null || !(o instanceof Water)) {
                return false;
            }
            Water oo = (Water) o;
            return x == oo.x && y == oo.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
    }

    public static boolean canMeasureWater(int x, int y, int z) {
        if (z < 0 || z > x + y) {
            return false;
        }
        Set set = new HashSet<>();
        Stack stack = new Stack<>();
        stack.push(new Water(0, 0));
        set.add(new Water(0,0));
        while (!stack.isEmpty()){
            Water water = stack.pop();
            if(water.x == z || water.y == z || water.x + water.y == z){
                return true;
            }

            // 将y倒入x 至x满或者y空
            if(water.y > 0 && water.x < x && water.y + water.x >= x){
                int nextX = x;
                int nextY = water.y + water.x - x;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }else if(water.y > 0 && water.x < x && water.y + water.x < x){
                int nextX = water.x + water.y;
                int nextY = 0;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }

            // 将x倒入y 至y满或者x空
            if(water.x > 0 && water.y < y && water.y + water.x >= y){
                int nextX = water.x + water.y - y;
                int nextY = y;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }else if(water.x > 0 && water.y < y && water.y + water.x < y){
                int nextX = 0;
                int nextY = water.y + water.x;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }

            // 将x装满
            if(water.x < x){
                int nextX = x;
                int nextY = water.y;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }

            // 将y装满
            if(water.y < y){
                int nextX = water.x;
                int nextY = y;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }

            // 将x倒光
            if(water.x > 0){
                int nextX = 0;
                int nextY = water.y;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }

            // 将y倒光
            if(water.y > 0){
                int nextX = water.x;
                int nextY = 0;
                Water nextWater = new Water(nextX, nextY);
                if(!set.contains(nextWater)){
                    stack.push(nextWater);
                    set.add(nextWater);
                }
            }
        }
        return false;
    }

数学方法
如果数学基础好的人,通过贝祖定理告诉我们,只需要找到 x, y的最大公约数并判断 z是否是它的倍数即可。所以本题就成了使用辗转相除法求解x,y的最大公约数。gcd(a,b) = gcd(b,a mod b)。

public static boolean canMeasureWater(int x, int y, int z) {
        if(z < 0 || z > x + y){
            return false;
        }
        if(x == 0 || y == 0){
            return z == 0 || z == Math.max(x, y);
        }
        int gcd = getGCD(x, y);
        if(z % gcd == 0){
            return true;
        }
        return false;
    }

    public static int getGCD(int x, int y){
        int a = Math.min(x, y);
        int b = Math.max(x, y);
        int mod = b % a;
        while (mod != 0){
            int temp = mod;
            mod = a % mod;
            a = temp;
        }
        return a;
    }

字典序排数


递归
1)按顺序把以1开头、2开头、...、9开头的小于等于n的所有数加入到list中
2)对于以1开头的,递归地将以0开头、1开头、2开头、...、9开头的数加入到list中;其它的都是这样。

    public static List lexicalOrder(int n) {
        List list = new ArrayList<>();
        for(int i=1; i<=9; i++){
            dfs(list, i, n);
        }

        return list;
    }

    public static void dfs(List list, int currentValue, int n){
        if(currentValue <= n){
            list.add(currentValue);
        }else {
            return;
        }

        for(int nextBit = 0; nextBit <= 9; nextBit ++){
            dfs(list, currentValue * 10 + nextBit, n);
        }

        return;
    }

求根到叶子节点数字之和



递归

  • 每层的数求和
  • 每一层的每个数:截止上一层得到的数乘10加上自身的值
     public int sumNumbers(TreeNode root) {
        if(root == null){
            return 0;
        }
        return recursion(root, 0);
    }

    public int recursion(TreeNode node, int preValue){
        if(node == null){
            return 0;
        }
        int temp = preValue * 10 + node.val;
        if(node.left == null && node.right == null){
            return temp;
        }
        return recursion(node.left, temp) + recursion(node.right, temp);
    }

回溯

  • 比较容易和直观地想法是将所有根到叶子节点的路径求得,在求和。
  • 这种方法稍慢,因为路径共同的部分,计算就重复了
public int sumNumbers(TreeNode root) {
        if(root == null){
            return 0;
        }
        List> numbers = new ArrayList<>();
        backtrace(numbers, new ArrayList<>(), root);
        int sum = 0;
        for(List number : numbers){
            sum += getNumber(number);
        }
        return sum;
    }

    public void backtrace(List> numbers, List number, TreeNode node){
        if(node.left == null && node.right == null){
            List newNumber = new ArrayList<>(number);
            newNumber.add(node.val);
            numbers.add(newNumber);
            return;
        }

        number.add(node.val);

        if(node.left != null){
            backtrace(numbers, number, node.left);
        }

        if(node.right != null){
            backtrace(numbers, number, node.right);
        }

        number.remove(number.size()-1);
    }

    public int getNumber(List bits){
        int number = 0;
        for(Integer bit : bits){
            number = number * 10 + bit;
        }
        return number;
    }

有序链表转换二叉搜索树

递归算法

  • 通过list获取链表所有元素,从而可以使用下标获取中间元素
  • 递归建立平衡二叉树即可
    public TreeNode sortedListToBST(ListNode head) {
        List numbers = new ArrayList<>();
        while (head != null){
            numbers.add(head.val);
            head = head.next;
        }
        return genTree(numbers, 0, numbers.size()-1);
    }

    public TreeNode genTree(List numbers, int start, int end){
        if(start == end){
            TreeNode treeNode = new TreeNode(numbers.get(start));
            return treeNode;
        }
        if(start > end){
            return null;
        }
        int med = (start + end) / 2;
        TreeNode treeNode = new TreeNode(numbers.get(med));
        treeNode.left = genTree(numbers, start, med - 1);
        treeNode.right = genTree(numbers, med + 1, end);
        return treeNode;
    }

树的子结构


递归

    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if(A == null || B == null){
            return false;
        }
        return contains(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
    }

    public boolean contains(TreeNode A, TreeNode B){
        if(B == null){
            return true;
        }
        if((A == null && B != null) || A.val != B.val){
            return false;
        }
        return contains(A.left, B.left) && contains(A.right, B.right);
    }

132模式


单调栈

  • left,med,right三者,判断满足132模式,至少需要两次比较:right>left && med>right
  • 所以我们需要找到一个med,使得med>right,并且这个right必定大于med左边的任意一个数;
  • 基于上述的思路,我们可以考虑从后向前遍历数组;因为我们要判断right是否大于med左边的任意一个数,所以联想到维护一个最小值前缀数组;我们还要判断med是否大于med之后的任意一个数,常规来说这需要O(N)的时间复杂度,为了降低时间复杂度,我们联想到维护一个单调递减栈,这样med只需要和栈顶的最小数进行比较即可做出判断。
  • 技巧使用:维护单调递减栈的方法是只有小于栈顶元素才可以入栈;维护单调递增栈的方法是只有大于栈顶元素才可以入栈;
  • 此方法时间复杂度为O(N)
    public boolean find132pattern(int[] nums) {
        Stack stack = new Stack<>();
        int n = nums.length;
        if(n < 3){
            return false;
        }
        int[] min = new int[n];
        min[0] = nums[0];
        for (int i = 1; i < n; i++) {
            min[i] = Math.min(nums[i], min[i - 1]);
        }
        for (int j = n - 1; j >= 0; j--) {
            if (nums[j] > min[j]) { // 保证入栈的right候选者必定大于任意一个左边的数
                while (!stack.isEmpty() && stack.peek() <= min[j]) { // 维护单调递减栈
                    stack.pop(); 
                }
                if (!stack.isEmpty() && nums[j] > stack.peek()) {
                    return true;
                }
                stack.push(nums[j]);
            }
        }
        return false;
    }

常规遍历

    public boolean find132pattern(int[] nums) {
        int left = 0;
        for (int med = left + 1; med < nums.length - 1; med++) {
            if (nums[med] > nums[left] + 1) {
                for (int right = med + 1; right < nums.length; right++) {
                    if(nums[right] < nums[med] && nums[right] > nums[left]){
                        return true;
                    }
                }
            }else if(nums[med] < nums[left]){
                left = med;
            }
        }
        return false;
    }

验证栈序列


使用栈模拟过程

  • 可以从入栈数组出发解题,也可以从出栈数组出发解题,代码如下
    // 从入栈数组出发解题
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        int N = pushed.length;
        Stack stack = new Stack();

        int j = 0;
        for (int x: pushed) {
            stack.push(x);
            while (!stack.isEmpty() && j < N && stack.peek() == popped[j]) {
                stack.pop();
                j++;
            }
        }

        return j == N;
    }

    // 从出栈数组出发解题
    public boolean validateStackSequences(int[] pushed, int[] popped) {
        Stack stack = new Stack<>();
        int n = pushed.length;
        int pushIndex = 0;
        for (int popIndex = 0; popIndex < n; popIndex++) {
            int targetPop = popped[popIndex];
            if(!stack.isEmpty() && stack.peek() == targetPop){
                stack.pop();
                continue;
            }
            while (pushIndex < n && pushed[pushIndex] != targetPop) {
                stack.push(pushed[pushIndex]);
                pushIndex++;
            }
            if(pushIndex < n){
                pushIndex++;
            }else if(stack.pop() != targetPop){
                return false;
            }
        }
        return true;
    }

求1+2+…+n


递归

    public int sumNums(int n) {
        return n == 0 ? 0 : n + sumNums(n-1);
    }

下一个更大元素 II

单调栈

  • 先不考虑环形问题,寻找下一个更大元素,我们维护一个递减栈(不增栈),若当前元素大于栈顶元素,此时栈顶元素出栈,对应下标的下一个更大元素已经确定就是当前元素;若当前元素小于等于栈顶元素,入栈。当遍历一遍之后,能够确定答案的只有一部分。剩下没有确定答案的数组下标还在栈中。所以我们需要再遍历一遍。
  • 对于环形问题,一般的解决技巧是利用求模运算得到下标;所以遍历两遍数组,只需要将数组的边界扩大一倍即可。
  • 由于要遍历两遍数组,对于第二遍,已经确定答案的,就不需要再进行计算了,所以维护了一个visit数组。
int[] arr = {1,2,3,4,5};
int n = arr.length, index = 0;
while (true) {
    print(arr[index % n]);
    index++;
}

所以此题的代码如下:

public int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];
        if(n == 0){
            return ans;
        }
        boolean[] visit = new boolean[n];
        Stack stack = new Stack<>();
        stack.push(0);
        for (int i = 1; i < 2 * n; i++) {
            int ind = i % n;
            while (!stack.isEmpty() && nums[ind] > nums[stack.peek()]) {
                int popInd = stack.pop();
                if(!visit[popInd]){
                    ans[popInd] = nums[ind];
                    visit[popInd] = true;
                }
            }
            stack.push(ind);
        }
        while (!stack.isEmpty()){
            int popInd = stack.pop();
            if(!visit[popInd]){
                ans[popInd] = -1;
                visit[popInd] = true;
            }
        }
        return ans;
    }

基本计算器 II


    public int calculate(String s) {
        Stack numStack = new Stack<>();

        char lastOp = '+';
        char[] arr = s.toCharArray();
        for(int i = 0; i < arr.length; i ++){
            if(arr[i] == ' ') continue;

            if(Character.isDigit(arr[i])){
                int tempNum = arr[i] - '0';
                while(++i < arr.length && Character.isDigit(arr[i])){
                    tempNum = tempNum * 10 + (arr[i] - '0');
                }
                i--;

                if(lastOp == '+') numStack.push(tempNum);
                else if(lastOp == '-') numStack.push(-tempNum);
                else numStack.push(res(lastOp, numStack.pop(), tempNum));
            } else lastOp = arr[i];
        }

        int ans = 0;
        for(int num : numStack) ans += num;
        return ans;
    }

    private int res(char op, int a, int b){
        if(op == '*') return a * b;
        else if(op == '/') return a / b;
        else if(op == '+') return a + b; //其实加减运算可以忽略
        else return a - b;
    }

你可能感兴趣的:(栈、递归(深度优先))