【题解】—— LeetCode一周小结7

🌟欢迎来到 我的博客 —— 探索技术的无限可能!


🌟博客的简介(文章目录)


【题解】—— 每日一道题目栏


上接:【题解】—— LeetCode一周小结6

知识:数据结构——树(前 中 后 bfs dfs)

12.二叉树的后序遍历

题目链接:145. 二叉树的后序遍历

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。

示例 1:

在这里插入图片描述

输入:root = [1,null,2,3]

输出:[3,2,1]

示例 2:

输入:root = []

输出:[]

示例 3:

输入:root = [1]

输出:[1]

提示:

树中节点的数目在范围 [0, 100] 内

-100 <= Node.val <= 100

进阶:递归算法很简单,你可以通过迭代算法完成吗?

题解:
方法一:递归遍历
        先递归左子树,接着递归右子树,再访问根节点。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
import java.util.*;

class Solution {
    private List<Integer> ans = new ArrayList<>(); // 用于存放后序遍历结果的列表

    /**
     * 执行后序遍历,返回遍历结果列表。
     * @param root 二叉树的根节点
     * @return 后序遍历结果列表
     */
    public List<Integer> postorderTraversal(TreeNode root) {
        dfs(root); // 调用深度优先搜索函数进行后序遍历
        return ans; // 返回后序遍历结果列表
    }

    /**
     * 深度优先搜索函数,递归遍历二叉树,并将节点值存入结果列表。
     * @param root 当前处理的节点
     */
    private void dfs(TreeNode root) {
        if (root == null) { // 递归终止条件:当前节点为空
            return;
        }
        dfs(root.left); // 递归遍历左子树
        dfs(root.right); // 递归遍历右子树
        ans.add(root.val); // 将当前节点值加入结果列表(在递归后面加入)
    }
}


方法二:栈实现非递归遍历
        在这个后序遍历的实现中,使用了一个额外的辅助栈和一个变量 prev 来记录上一个访问过的节点。算法的核心思想是在访问每个节点时,先判断其右子节点是否被访问过,如果右子节点未被访问过,则先遍历右子树;如果右子节点已被访问过或不存在,则将当前节点的值加入结果列表,并将其弹出栈,并更新 prev 为当前节点。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
import java.util.*;

class Solution {
    /**
     * 执行后序遍历,返回遍历结果列表。
     * @param root 二叉树的根节点
     * @return 后序遍历结果列表
     */
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>(); // 用于存放后序遍历结果的列表
        Deque<TreeNode> stk = new ArrayDeque<>(); // 辅助栈,用于遍历过程中保存节点
        TreeNode prev = null; // 用于记录上一个访问过的节点

        // 遍历二叉树
        while (root != null || !stk.isEmpty()) {
            if (root != null) {
                stk.push(root); // 将当前节点入栈
                root = root.left; // 移动到左子节点
            } else {
                TreeNode peekNode = stk.peek(); // 查看栈顶节点,但不出栈
                if (peekNode.right != null && peekNode.right != prev) { // 如果栈顶节点的右子节点存在且未被访问过
                    root = peekNode.right; // 移动到右子节点
                } else { // 如果栈顶节点的右子节点不存在或已被访问过
                    stk.pop(); // 栈顶节点出栈
                    ans.add(peekNode.val); // 将栈顶节点的值加入结果列表
                    prev = peekNode; // 更新上一个访问过的节点
                }
            }
        }
        return ans; // 返回后序遍历结果列表
    }
}



方法三:Morris 实现中序遍历

        这段代码使用了 Morris 遍历的思想实现了二叉树的后序遍历,其时间复杂度为 O(n),其中 n 是二叉树中的节点数。Morris 遍历算法的优点是空间复杂度为 O(1),不需要额外的栈空间。在后序遍历中,需要额外的操作来将遍历路径上的节点逆序添加到结果列表中,这部分操作在 reverseAddNodes 方法中实现。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
import java.util.*;

class Solution {
    /**
     * 执行 Morris 实现的后序遍历,返回遍历结果列表。
     * @param root 二叉树的根节点
     * @return 后序遍历结果列表
     */
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>(); // 用于存放后序遍历结果的列表
        TreeNode dummy = new TreeNode(-1); // 添加一个虚拟节点作为辅助节点
        dummy.left = root; // 将虚拟节点的左子节点指向根节点
        TreeNode cur = dummy; // 初始化当前节点为虚拟节点

        // 遍历二叉树
        while (cur != null) {
            if (cur.left == null) { // 如果当前节点的左子节点为空
                cur = cur.right; // 移动到当前节点的右子节点
            } else { // 如果当前节点的左子节点不为空
                TreeNode prev = cur.left; // 找到当前节点的左子树的最右下角节点
                while (prev.right != null && prev.right != cur) {
                    prev = prev.right;
                }
                if (prev.right == null) { // 如果最右下角节点的右子节点为空
                    prev.right = cur; // 将最右下角节点的右子节点指向当前节点
                    cur = cur.left; // 移动到当前节点的左子节点
                } else { // 如果最右下角节点的右子节点不为空
                    reverseAddNodes(cur.left, prev, ans); // 将从当前节点的左子节点到 prev 节点这段路径上的节点,按照逆序添加到结果列表中
                    prev.right = null; // 恢复最右下角节点的右子节点为null
                    cur = cur.right; // 移动到当前节点的右子节点
                }
            }
        }
        return ans; // 返回后序遍历结果列表
    }

    /**
     * 将从 start 节点到 end 节点这段路径上的节点,按照逆序添加到结果列表中。
     * @param start 起始节点
     * @param end 终止节点
     * @param ans 结果列表
     */
    private void reverseAddNodes(TreeNode start, TreeNode end, List<Integer> ans) {
        reverseNodes(start, end); // 先翻转从 start 节点到 end 节点的路径上的节点
        TreeNode cur = end; // 初始化当前节点为 end 节点
        while (true) {
            ans.add(cur.val); // 将当前节点的值加入结果列表
            if (cur == start) { // 如果当前节点等于起始节点,结束循环
                break;
            }
            cur = cur.right; // 移动到当前节点的右子节点
        }
        reverseNodes(end, start); // 再次翻转从 start 节点到 end 节点的路径上的节点,恢复原状
    }

    /**
     * 翻转从 start 节点到 end 节点这段路径上的节点。
     * @param start 起始节点
     * @param end 终止节点
     */
    private void reverseNodes(TreeNode start, TreeNode end) {
        if (start == end) { // 如果起始节点等于终止节点,不需要翻转,直接返回
            return;
        }
        TreeNode x = start; // 初始化 x 节点为起始节点
        TreeNode y = start.right; // 初始化 y 节点为起始节点的右子节点
        while (true) {
            TreeNode z = y.right; // 初始化 z 节点为 y 节点的右子节点
            y.right = x; // 将 y 节点的右子节点指向 x 节点
            x = y; // 更新 x 节点为 y 节点
            y = z; // 更新 y 节点为 z 节点
            if (x == end) { // 如果 x 节点等于终止节点,结束循环
                break;
            }
        }
    }
}

13.二叉树的垂序遍历

题目链接:987. 二叉树的垂序遍历

给你二叉树的根结点 root ,请你设计算法计算二叉树的 垂序遍历 序列。

对位于 (row, col) 的每个结点而言,其左右子结点分别位于 (row + 1, col - 1) 和 (row + 1, col + 1) 。树的根结点位于 (0, 0) 。

二叉树的 垂序遍历 从最左边的列开始直到最右边的列结束,按列索引每一列上的所有结点,形成一个按出现位置从上到下排序的有序列表。如果同行同列上有多个结点,则按结点的值从小到大进行排序。

返回二叉树的 垂序遍历 序列。

示例 1:
在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]

输出:[[9],[3,15],[20],[7]]

解释:

列 -1 :只有结点 9 在此列中。

列 0 :只有结点 3 和 15 在此列中,按从上到下顺序。

列 1 :只有结点 20 在此列中。

列 2 :只有结点 7 在此列中。

示例 2:
在这里插入图片描述

输入:root = [1,2,3,4,5,6,7]

输出:[[4],[2],[1,5,6],[3],[7]]

解释:

列 -2 :只有结点 4 在此列中。

列 -1 :只有结点 2 在此列中。

列 0 :结点 1 、5 和 6 都在此列中。
        1 在上面,所以它出现在前面。
        5 和 6 位置都是 (2, 0) ,所以按值从小到大排序,5 在 6 的前面。

列 1 :只有结点 3 在此列中。

列 2 :只有结点 7 在此列中。

示例 3:

在这里插入图片描述

输入:root = [1,2,3,4,6,5,7]

输出:[[4],[2],[1,5,6],[3],[7]]

解释: 这个示例实际上与示例 2 完全相同,只是结点 5 和 6 在树中的位置发生了交换。

因为 5 和 6 的位置仍然相同,所以答案保持不变,仍然按值从小到大排序。

提示:

树中结点数目总数在范围 [1, 1000] 内

0 <= Node.val <= 1000

题解:
方法:深度优先DFS
        这段代码实现了二叉树的垂直遍历,将节点按照列号分组存储,并在每组中按照行号和节点值进行排序。整体思路是使用深度优先搜索遍历二叉树,在遍历的过程中记录每个节点的行号、列号和值,并将其分组存储在 groups 中。最后,对每个分组按照题目要求的顺序进行排序,并构造出最终的结果列表。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    /**
     * 执行垂直遍历,返回按垂直顺序遍历的节点值列表。
     * @param root 二叉树的根节点
     * @return 垂直遍历结果列表
     */
    public List<List<Integer>> verticalTraversal(TreeNode root) {
        // 使用 TreeMap 来保存分组,以确保按列顺序遍历
        Map<Integer, List<int[]>> groups = new TreeMap<>();
        
        // 执行深度优先搜索,记录节点的行号、列号和值
        dfs(root, 0, 0, groups);

        // 构造结果列表
        List<List<Integer>> ans = new ArrayList<>(groups.size());
        for (List<int[]> g : groups.values()) {
            // 对于同一列的节点,按照行号和值进行排序
            g.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
            List<Integer> vals = new ArrayList<>(g.size());
            // 将排序后的节点值加入结果列表
            for (int[] p : g) {
                vals.add(p[1]);
            }
            ans.add(vals);
        }
        return ans;
    }

    /**
     * 深度优先搜索函数,递归遍历二叉树,并记录节点的行号、列号和值。
     * @param node 当前处理的节点
     * @param row 当前节点的行号
     * @param col 当前节点的列号
     * @param groups 保存分组结果的映射表
     */
    private void dfs(TreeNode node, int row, int col, Map<Integer, List<int[]>> groups) {
        if (node == null) {
            return;
        }
        // 将当前节点加入对应列的分组中
        groups.computeIfAbsent(col, k -> new ArrayList<>()).add(new int[]{row, node.val});
        // 递归处理左右子节点,更新行号和列号
        dfs(node.left, row + 1, col - 1, groups);
        dfs(node.right, row + 1, col + 1, groups);
    }
}


方法二:在 DFS 的同时记录 col的最小值,这样无需对 key 排序,也无需使用有序字典。

        使用了 minCol 变量来记录最小的列号,而不是直接从 0 开始遍历 groups 映射表。这个变量的作用是确保我们的列号是从最小列号开始的,这样我们可以保证结果列表中的列是按照从左到右的顺序排列的。

具体区别在于以下几点:

  1. 使用 minCol 变量:我们初始化 minCol 为 0,并在遍历节点的过程中更新它。这样可以确保我们的列号是从最小列号开始的,而不是直接从 0 开始。这样做的好处是确保结果列表中的列是按照从左到右的顺序排列的。

  2. 构造结果列表时的列号遍历:在构造结果列表时,我们使用 minCol 变量作为起始列号,并以 minCol + groups.size() 作为结束列号。这样可以确保我们遍历到了所有的列号,并保证了结果列表中的列是按照从左到右的顺序排列的。

class Solution {
    private int minCol; // 用于记录最小列号

    /**
     * 执行垂直遍历,返回按垂直顺序遍历的节点值列表。
     * @param root 二叉树的根节点
     * @return 垂直遍历结果列表
     */
    public List<List<Integer>> verticalTraversal(TreeNode root) {
        Map<Integer, List<int[]>> groups = new HashMap<>(); // 用于保存分组结果的映射表
        dfs(root, 0, 0, groups); // 执行深度优先搜索遍历二叉树

        List<List<Integer>> ans = new ArrayList<>(groups.size());
        // 遍历每个分组,按照列号顺序构造结果列表
        for (int col = minCol; col < minCol + groups.size(); col++) {
            List<int[]> g = groups.get(col);
            g.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]); // 对每个分组进行排序
            List<Integer> vals = new ArrayList<>(g.size());
            // 将排序后的节点值加入结果列表
            for (int[] p : g) {
                vals.add(p[1]);
            }
            ans.add(vals);
        }
        return ans; // 返回垂直遍历结果列表
    }

    /**
     * 深度优先搜索函数,递归遍历二叉树,并记录节点的行号、列号和值。
     * @param node 当前处理的节点
     * @param row 当前节点的行号
     * @param col 当前节点的列号
     * @param groups 保存分组结果的映射表
     */
    private void dfs(TreeNode node, int row, int col, Map<Integer, List<int[]>> groups) {
        if (node == null) {
            return;
        }
        minCol = Math.min(minCol, col); // 更新最小列号
        groups.computeIfAbsent(col, k -> new ArrayList<>()).add(new int[]{row, node.val}); // 将当前节点加入对应列的分组中
        // 递归处理左右子节点,更新行号和列号
        dfs(node.left, row + 1, col - 1, groups);
        dfs(node.right, row + 1, col + 1, groups);
    }
}


方法三:双数组

        这段代码实现了二叉树的垂直遍历,并将节点按照列号分组存储在 leftright 中,一个列表 left 负责统计负数 col,另一个列表 right负责统计非负数 col

        然后将分组中的节点按照行号和节点值排序,最后将排序后的节点值添加到结果列表中。

        整体思路是使用深度优先搜索遍历二叉树,在遍历的过程中将节点分组存储,并按照要求进行排序和组装结果列表。

相当于用两个列表来模拟双端队列。

class Solution {
    /**
     * 执行垂直遍历,返回按垂直顺序遍历的节点值列表。
     * @param root 二叉树的根节点
     * @return 垂直遍历结果列表
     */
    public List<List<Integer>> verticalTraversal(TreeNode root) {
        List<List<int[]>> left = new ArrayList<>(); // 左侧分组列表
        List<List<int[]>> right = new ArrayList<>(); // 右侧分组列表
        dfs(root, 0, 0, left, right); // 执行深度优先搜索遍历二叉树,生成左右分组列表

        List<List<Integer>> ans = new ArrayList<>(left.size() + right.size()); // 结果列表
        Collections.reverse(left); // 反转左侧分组列表,使列号从小到大排序
        add(ans, left); // 添加左侧分组到结果列表
        add(ans, right); // 添加右侧分组到结果列表
        return ans; // 返回垂直遍历结果列表
    }

    /**
     * 深度优先搜索函数,递归遍历二叉树,并记录节点的行号、列号和值。
     * @param node 当前处理的节点
     * @param row 当前节点的行号
     * @param col 当前节点的列号
     * @param left 左侧分组列表
     * @param right 右侧分组列表
     */
    private void dfs(TreeNode node, int row, int col, List<List<int[]>> left, List<List<int[]>> right) {
        if (node == null) {
            return;
        }
        // 根据节点的列号确定分组,将节点添加到对应的分组中
        if (col < -left.size()) {
            left.add(new ArrayList<>());
        } else if (col == right.size()) {
            right.add(new ArrayList<>());
        }
        (col < 0 ? left.get(-col - 1) : right.get(col)).add(new int[]{row, node.val});
        // 递归处理左右子节点,更新行号和列号
        dfs(node.left, row + 1, col - 1, left, right);
        dfs(node.right, row + 1, col + 1, left, right);
    }

    /**
     * 将分组列表中的节点值添加到结果列表中。
     * @param ans 结果列表
     * @param a 分组列表
     */
    private void add(List<List<Integer>> ans, List<List<int[]>> a) {
        for (List<int[]> g : a) {
            Collections.sort(g, (p, q) -> p[0] != q[0] ? p[0] - q[0] : p[1] - q[1]); // 对分组中的节点按行号和值进行排序
            List<Integer> vals = new ArrayList<>(g.size());
            for (int[] p : g) {
                vals.add(p[1]); // 将排序后的节点值添加到结果列表
            }
            ans.add(vals); // 将结果列表加入到结果列表中
        }
    }
}

方法四:单数组

        把所有 (col,row,val) 全部丢到同一个列表中,排序后按照 col分组。

class Solution {
    public List<List<Integer>> verticalTraversal(TreeNode root) {
        List<int[]> data = new ArrayList<>();
        dfs(root, 0, 0, data);

        List<List<Integer>> ans = new ArrayList<>();
        data.sort((a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] != b[1] ? a[1] - b[1] : a[2] - b[2]);
        int lastCol = Integer.MIN_VALUE;
        for (int[] d : data) {
            if (d[0] != lastCol) {
                lastCol = d[0];
                ans.add(new ArrayList<>());
            }
            ans.get(ans.size() - 1).add(d[2]);
        }
        return ans;
    }

    private void dfs(TreeNode node, int row, int col, List<int[]> data) {
        if (node == null) {
            return;
        }
        data.add(new int[]{col, row, node.val});
        dfs(node.left, row + 1, col - 1, data);
        dfs(node.right, row + 1, col + 1, data);
    }
}



14.二叉树的层序遍历

题目链接:102. 二叉树的层序遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:

在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]

输出:[[3],[9,20],[15,7]]

示例 2:

输入:root = [1]

输出:[[1]]

示例 3:

输入:root = []

输出:[]

提示:

树中节点数目在范围 [0, 2000] 内

-1000 <= Node.val <= 1000

题解:
方法:迭代

        广度优先遍历是按层层推进的方式,遍历每一层的节点。题目要求的是返回每一层的节点值,所以这题用广度优先来做非常合适。

        广度优先需要用队列作为辅助结构,我们先将根节点放到队列中,然后不断遍历队列。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
import java.util.*;	
class Solution {
	public List<List<Integer>> levelOrder(TreeNode root) {
		if(root==null) {
			return new ArrayList<List<Integer>>();
		}
		
		List<List<Integer>> res = new ArrayList<List<Integer>>();
		LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
		//将根节点放入队列中,然后不断遍历队列
		queue.add(root);
		while(queue.size()>0) {
			//获取当前队列的长度,这个长度相当于 当前这一层的节点个数
			int size = queue.size();
			ArrayList<Integer> tmp = new ArrayList<Integer>();
			//将队列中的元素都拿出来(也就是获取这一层的节点),放到临时list中
			//如果节点的左/右子树不为空,也放入队列中
			for(int i=0;i<size;++i) {
				TreeNode t = queue.remove();
				tmp.add(t.val);
				if(t.left!=null) {
					queue.add(t.left);
				}
				if(t.right!=null) {
					queue.add(t.right);
				}
			}
			//将临时list加入最终返回结果中
			res.add(tmp);
		}
		return res;
	}
}

方法:递归

import java.util.*;	
class Solution {
	public List<List<Integer>> levelOrder(TreeNode root) {
		if(root==null) {
			return new ArrayList<List<Integer>>();
		}
		//用来存放最终结果
		List<List<Integer>> res = new ArrayList<List<Integer>>();
		dfs(1,root,res);
		return res;
	}
	
	void dfs(int index,TreeNode root, List<List<Integer>> res) {
		//假设res是[ [1],[2,3] ], index是3,就再插入一个空list放到res中
		if(res.size()<index) {
			res.add(new ArrayList<Integer>());
		}
		//将当前节点的值加入到res中,index代表当前层,假设index是3,节点值是99
		//res是[ [1],[2,3] [4] ],加入后res就变为 [ [1],[2,3] [4,99] ]
		res.get(index-1).add(root.val);
		//递归的处理左子树,右子树,同时将层数index+1
		if(root.left!=null) {
			dfs(index+1, root.left, res);
		}
		if(root.right!=null) {
			dfs(index+1, root.right, res);
		}
	}
}

15.二叉树的层序遍历 II

题目链接:107. 二叉树的层序遍历 II

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

示例 1:

在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]

输出:[[15,7],[9,20],[3]]

示例 2:

输入:root = [1]

输出:[[1]]

示例 3:

输入:root = []

输出:[]

提示:

树中节点数目在范围 [0, 2000] 内

-1000 <= Node.val <= 1000

题解:
方法:BFS

        BFS遍历比较直观,就是按层遍历
在这里插入图片描述

        我们将我们一层遍历得到的结果放到一个数组中,等这一层遍历完后,将这个数组放入到最终结果集res中。

        因为题目要求是反序输出,所以我们只需将res翻转一下就可以了。

在这里插入图片描述

        如上图,首先创建一个队列,将根节点root放入队列中,然后不断循环遍历这个队列。

        对于树的第一层,其长度为1,而此时根节点也放到队列中了,所以队列的长度也是1,我们取出一个元素,将其放入临时数组中。

        如果这个节点的左右孩子不为空,则将他们也放入队列中。
在这里插入图片描述

        上图是处理第二层的过程,类似于第一层,由于第一层遍历完后,队列里有两个元素了,所以第二次遍历的时候,可以确定队列长度是2,于是执行两次,将2和3取出,放入临时数组中,之后再将2和3的两个孩子也放入队列中。

        处理第三层时,队列长度就是4了,于是取出4个元素,再次处理。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        if(root==null) {
            return new ArrayList<List<Integer>>();
        }
        //用来存放最终结果
        ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
        //创建一个队列,将根节点放入其中
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            //每次遍历的数量为队列的长度
            int size = queue.size();
            ArrayList<Integer> tmp = new ArrayList<Integer>();
            //将这一层的元素全部取出,放入到临时数组中,如果节点的左右孩子不为空,继续放入队列
            for(int i=0;i<size;++i) {
                TreeNode node = queue.poll();
                tmp.add(node.val);
                if(node.left!=null) {
                    queue.offer(node.left);
                }
                if(node.right!=null) {
                    queue.offer(node.right);
                }
            }
            res.add(tmp);
        }
        //翻转最终结果并返回
        Collections.reverse(res);
        return res;
    }
}

方法:BFS 2
        第一种方式中,我们将最终结果集定义成数组,等所有元素都放置完后,再翻转一下数组。

        我们可以将其结构改成链表,以后每层遍历完将结果放入到链表的第一位,这样就自动完成了翻转的功能了。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        if(root==null) {
            return new ArrayList<List<Integer>>();
        }
        //改用链表实现,每次都插入到第一位
        LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            int size = queue.size();
            ArrayList<Integer> tmp = new ArrayList<Integer>();
            for(int i=0;i<size;++i) {
                TreeNode node = queue.poll();
                tmp.add(node.val);
                if(node.left!=null) {
                    queue.offer(node.left);
                }
                if(node.right!=null) {
                    queue.offer(node.right);
                }
            }
            //每次都保存到链表的第一位,这样自带了翻转的功能
            res.add(0,tmp);
        }
        return res;
    }
}

方法:DFS

        DFS实现就不那么直观了,我们可以把二叉树结构改变一下,想象成一个三角形的结构

        我们在递归遍历时,将第一层的元素放入到第一层的数组中,将第二层的的元素放入到第二层,第三层的元素放入第三层。

        第三层的4是在5之前被遍历的,所以放入到第三层数组时,4就在5前面,同理6是在7前面被遍历的,所以放入到第三层时6就在7前面。
在这里插入图片描述

        这次我们不用创建队列了,直接在最终结果集res上操作,res是个二维数组,集合中嵌套集合,但现在没有任何嵌套的集合。

在这里插入图片描述

        如上图所示,一开始res是空的,当遍历到第一层时候,我们创建一个集合,放入res,然后将第一层的1,放到res[0]中。

        遍历到第二层时,再创建一个集合放入res,然后将2放入res[1]中。

        遍历到第三层时,同样再创建一个集合放入res,将4放入res[2]中。

        当我们再回到第三层,遍历到5时,就不用再创建新集合了,直接将5放到到res[2]中即可。

        之后,再将3放到res[1],6、7放入res[2]中。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        if(root==null) {
            return new ArrayList<List<Integer>>();
        }
        //用来存放最终结果
        ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
        dfs(root,res,1);
        Collections.reverse(res);
        return res;
    }

    private void dfs(TreeNode root,ArrayList<List<Integer>> res,int index) {
        if(root==null) {
            return;
        }
        //如果index大于res大小,说明这一层没有对应的集合,需要新创建
        if(index>res.size()) {
            res.add(new ArrayList<Integer>());
        }
        //将当前层的元素直接放到对应层的末尾即可
        res.get(index-1).add(root.val);
        //继续遍历左右节点,同时将层高+1
        dfs(root.left,res,index+1);
        dfs(root.right,res,index+1);
    }
}


16.二叉树的锯齿形层序遍历

题目链接:103. 二叉树的锯齿形层序遍历
给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

示例 1:
在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]

输出:[[3],[20,9],[15,7]]

示例 2:

输入:root = [1]

输出:[[1]]

示例 3:

输入:root = []

输出:[]

提示:

树中节点数目在范围 [0, 2000] 内

-100 <= Node.val <= 100

题解:
方法:BFS
        二叉树的BFS打印,就是一层一层的往下打印,因为这题要求的是先从左边,下一层从右边,在下一次从左边……左右交替的。我们可以使用一个变量leftToRight,如果是true就表示从左边开始打印,否则就从右边开始打印。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null)
            return res;
        //创建队列,保存节点
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);//先把节点加入到队列中
        boolean leftToRight = true;//第一步先从左边开始打印
        while (!queue.isEmpty()) {
            //记录每层节点的值
            List<Integer> level = new ArrayList<>();
            //统计这一层有多少个节点
            int count = queue.size();
            //遍历这一层的所有节点,把他们全部从队列中移出来,顺便
            //把他们的值加入到集合level中,接着再把他们的子节点(如果有)
            //加入到队列中
            for (int i = 0; i < count; i++) {
                //poll移除队列头部元素(队列在头部移除,尾部添加)
                TreeNode node = queue.poll();
                //判断是从左往右打印还是从右往左打印。
                if (leftToRight) {
                    //如果从左边打印,直接把访问的节点值加入到列表level的末尾即可
                    level.add(node.val);
                } else {
                    //如果是从右边开始打印,每次要把访问的节点值
                    //加入到列表的最前面
                    level.add(0, node.val);
                }
                //左右子节点如果不为空会被加入到队列中
                if (node.left != null)
                    queue.add(node.left);
                if (node.right != null)
                    queue.add(node.right);
            }
            //把这一层的节点值加入到集合res中
            res.add(level);
            //改变下次访问的方向
            leftToRight = !leftToRight;
        }
        return res;
    }
}

方法:DFS
        这题除了使用BFS以外,还可以使用DFS,但这里要有个判断,如果走到下一层的时候集合没有创建,要先创建下一层的集合。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        travel(root, res, 0);
        return res;
    }

    private void travel(TreeNode cur, List<List<Integer>> res, int level) {
        if (cur == null)
            return;
        //如果res.size() <= level说明下一层的集合还没创建,所以要先创建下一层的集合
        if (res.size() <= level) {
            List<Integer> newLevel = new LinkedList<>();
            res.add(newLevel);
        }
        //遍历到第几层我们就操作第几层的数据
        List<Integer> list = res.get(level);
        //这里默认根节点是第0层,偶数层相当于从左往右遍历,
        // 所以要添加到集合的末尾,如果是奇数层相当于从右往左遍历,
        // 要把数据添加到集合的开头
        if (level % 2 == 0)
            list.add(cur.val);
        else
            list.add(0, cur.val);
        //分别遍历左右两个子节点,到下一层了,所以层数要加1
        travel(cur.left, res, level + 1);
        travel(cur.right, res, level + 1);
    }
}

17.N 叉树的层序遍历

题目链接:429. N 叉树的层序遍历

给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。

树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

示例 1:
在这里插入图片描述

输入:root = [1,null,3,2,4,null,5,6]

输出:[[1],[3,2,4],[5,6]]

示例 2:

在这里插入图片描述

输入:root =
[1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]

输出:[[1],[2,3,4,5],[6,7,8,9,10],[11,12,13],[14]]

提示:

树的高度不会超过 1000

树的节点总数在 [0, 10^4] 之间

题解:树的(层序)遍历运用题
方法:BFS
        我们需要以「层」为单位构建答案,因此在单次 BFS 过程中也按层进行。

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    public List<List<Integer>> levelOrder(Node root) {
        List<List<Integer>> ans = new ArrayList<>();
        Deque<Node> d = new ArrayDeque<>();
        if (root != null) d.addLast(root);
        while (!d.isEmpty()) {
            int size = d.size();
            List<Integer> list = new ArrayList<>();
            while (size-- > 0) {
                Node t = d.pollFirst();
                for (Node node : t.children) d.addLast(node);
                list.add(t.val);
            }
            ans.add(list);
        }
        return ans;
    }
}

18.N 叉树的前序遍历

题目链接:589. N 叉树的前序遍历

给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。

n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。

示例 1:

在这里插入图片描述

输入:root = [1,null,3,2,4,null,5,6]

输出:[1,3,5,6,2,4]

示例 2:

在这里插入图片描述

输入:root =
[1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]

输出:[1,2,3,6,7,11,14,4,8,12,5,9,13,10]

提示:

节点总数在范围 [0, 104]内

0 <= Node.val <= 104

n 叉树的高度小于或等于 1000

题解:
方法:递归
        

class Solution {
    List<Integer> ans = new ArrayList<>();
    public List<Integer> preorder(Node root) {
        dfs(root);
        return ans;
    }
    void dfs(Node root) {
        if (root == null) return ;
        ans.add(root.val);
        for (Node node : root.children) dfs(node);
    }
}

方法:迭代
        使用栈模拟递归过程。

        迭代过程中记录 (node = 当前节点, cnt = 当前节点遍历过的子节点数量) 二元组,每次取出栈顶元素,如果当前节点是首次出队(当前遍历过的子节点数量为 cnt=0),则将当前节点的值加入答案,然后更新当前元素遍历过的子节点数量,并重新入队,即将 (node,cnt+1)入队,以及将下一子节点 (node.children[cnt],0) 进行首次入队。

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> ans = new ArrayList<>();
        Deque<Object[]> d = new ArrayDeque<>();
        d.addLast(new Object[]{root, 0});
        while (!d.isEmpty()) {
            Object[] poll = d.pollLast();
            Node t = (Node)poll[0]; Integer cnt = (Integer)poll[1];
            if (t == null) continue;
            if (cnt == 0) ans.add(t.val);
            if (cnt < t.children.size()) {
                d.addLast(new Object[]{t, cnt + 1});
                d.addLast(new Object[]{t.children.get(cnt), 0});
            }
        }
        return ans;
    }
}

方法:通用 递归 转 迭代
        这是直接模拟系统执行递归的过程,这是一种更为通用的做法。在迭代过程中记录当前栈帧位置状态 loc,在每个状态流转节点做相应操作。

由于现代编译器已经做了很多关于递归的优化,现在这种技巧已经无须掌握。

class Solution {
    public List<Integer> preorder(Node root) {
        List<Integer> ans = new ArrayList<>();
        Deque<Object[]> d = new ArrayDeque<>();
        d.addLast(new Object[]{0, root});
        while (!d.isEmpty()) {
            Object[] poll = d.pollLast();
            Integer loc = (Integer)poll[0]; Node t = (Node)poll[1];
            if (t == null) continue;
            if (loc == 0) {
                ans.add(t.val);
                d.addLast(new Object[]{1, t});
            } else if (loc == 1) {
                int n = t.children.size();
                for (int i = n - 1; i >= 0; i--) {
                    d.addLast(new Object[]{0, t.children.get(i)});
                }
            }
        }
        return ans;
    }
}

下接:【题解】—— LeetCode一周小结8


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZShiJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值