力扣 102题 二叉树的层次遍历 记录

题目描述

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

示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例 2:
输入:root = [1]
输出:[[1]]

示例 3:
输入:root = []
输出:[]

思路

队列是一种先进先出(FIFO)的数据结构,非常适合于层序遍历,因为我们总是希望按照从树的上层到下层的顺序处理节点。

1、初始化阶段

  • 首先,检查传入的根节点是否为null。如果不为null,则将根节点加入到队列中。这个步骤是层序遍历的启动点,确保了从树的根节点开始遍历。

2、遍历阶段

  • 队列操作:使用一个队列来管理待处理的树节点。队列的先进先出特性保证了树的每一层都按照从左到右的顺序被处理。

  • 层级处理:

    • 循环开始时,首先检查队列是否为空。只要队列中还有节点,就继续遍历。
    • 对于每一层,首先获取队列的当前大小,这个大小表示该层的节点数量。
    • 初始化一个向量vec,用于存储当前层所有节点的值。
  • 节点处理:

    • 对当前层中的每个节点进行循环处理。循环中,每次从队列中取出队列前端的节点(当前节点),并从队列中移除它。
    • 将当前节点的值加入到当前层的向量vec中。
    • 检查当前节点是否有左子节点和右子节点。如果有,将这些子节点按顺序加入到队列中,以便在后续的层次中遍历它们。
  • 存储层次数据:

    • 完成当前层的节点处理后,将当前层的向量vec添加到最终结果的向量result中。这样,result逐步构建成一个包含所有层次数据的二维向量。

3、结束阶段

  • 当队列为空时,循环结束,此时所有树节点都已按层序遍历完毕。
  • 函数返回最终的二维向量result,这个向量现在包含了树的层序遍历结果,其中每个内部向量代表树的一层。

4、总体来看

  • 时间复杂度:每个节点恰好被访问一次,因此时间复杂度为O(n),其中n是树中的节点总数。
  • 空间复杂度:在最坏的情况下(完全二叉树),队列可能需要存储大约n/2个节点(最后一层的节点),因此空间复杂度也是O(n)。

完整代码

#include<iostream>
#include<queue>
#include<vector>

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode() : val(0), left(nullptr), right(nullptr) {}
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
    TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) { // 接受一个 TreeNode* 类型的参数,返回一个 vector<vector<int>> 类型的二维向量。
        queue<TreeNode*> que; // 用于按层序存储树中的节点,以便按顺序访问
        if(root != NULL) que.push(root);
        vector<vector<int>> result;
        while(!que.empty()){
            int size = que.size();
            vector<int> vec;
            for(int i = 0; i < size; i++){
                TreeNode* node = que.front(); 
                que.pop();
                vec.push_back(node->val);
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};

int main() {
    // 创建特定的二叉树:[3,9,20,null,null,15,7]
    TreeNode* root = new TreeNode(3);
    root->left = new TreeNode(9);
    root->right = new TreeNode(20);
    root->right->left = new TreeNode(15);
    root->right->right = new TreeNode(7);

    Solution solution;
    vector<vector<int>> result = solution.levelOrder(root);

    for (const vector<int>& level : result) {
        for (int val : level) {
            cout << val << " ";
        }
        cout << endl;
    }

    return 0;
}
### 力扣二叉树中序遍历的Java实现思路 #### 方法一:递归解法 递归是一种直观且易于理解的方法。通过定义一个辅助函数 `dfs`,按照 **左子树 -> 根节点 -> 右子树** 的顺序依次处理每个节点。对于每一个节点,如果它不为空,则先递归遍历其左子树,接着将该节点的值加入结果列表,最后递归遍历右子树。 以下是具体的代码实现: ```java class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); dfs(res, root); return res; } private void dfs(List<Integer> res, TreeNode node) { if (node == null) { return; } dfs(res, node.left); // 递归遍历左子树 res.add(node.val); // 访问根节点 dfs(res, node.right); // 递归遍历右子树 } } ``` 这种方法的时间复杂度和空间复杂度均为 O(n),其中 n 是二叉树中的节点数[^3]。 --- #### 方法二:迭代解法 迭代方法的核心思想是手动模拟递归过程中使用的栈结构。由于递归本质上也是利用系统栈完成操作,因此可以通过显式的栈来替代系统的隐式调用栈。 具体步骤如下: 1. 初始化一个空的结果列表 `res` 和一个栈 `stack`。 2. 使用一个指针变量 `current` 指向当前正在访问的节点。 3. 当前节点不为空或栈不为空时进入循环: - 如果当前节点存在,将其压入栈并继续移动到它的左子节点; - 否则弹出栈顶元素(即最近一次未完全访问的节点),记录其值,并转向其右子节点。 4. 循环结束后返回结果列表。 下面是对应的代码实现: ```java class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Deque<TreeNode> stack = new LinkedList<>(); TreeNode current = root; while (current != null || !stack.isEmpty()) { while (current != null) { // 将所有左子节点压入栈 stack.push(current); current = current.left; } current = stack.pop(); // 弹出栈顶元素 res.add(current.val); // 添加到结果集 current = current.right; // 移动至右子节点 } return res; } } ``` 此方法同样具备时间复杂度 O(n)[^2] 和最坏情况下空间复杂度也为 O(n) 的特性。 --- #### 方法三:Morris 遍历 Morris 遍历是一种不需要额外存储空间的高效算法,其核心思想是在遍历时修改树的结构以建立临时链接,从而避免使用栈或递归来保存中间状态。最终恢复原始树形结构的同时完成了遍历。 主要逻辑分为以下几个部分: 1. 初始设置当前节点为根节点。 2. 对于每个节点,判断是否有左子树: - 若无左子树,则直接访问该节点并将指针移向右子树; - 若有左子树,则找到左子树中最右侧的节点(即前驱节点)并与当前节点连接起来;随后再次调整指针位置。 3. 继续上述过程直至整棵树被完整遍历完毕。 以下是 Morris 遍历的具体实现: ```java class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); TreeNode current = root; while (current != null) { if (current.left == null) { // 左子树不存在 res.add(current.val); // 直接访问当前节点 current = current.right; // 转移到右子树 } else { // 存在左子树 TreeNode predecessor = findPredecessor(current); if (predecessor.right == null) { // 前驱尚未连接回当前节点 predecessor.right = current; // 创建反向链路 current = current.left; // 进入左子树 } else { // 前驱已连接回来 predecessor.right = null; // 断开反向链路 res.add(current.val); // 访问当前节点 current = current.right; // 转移到右子树 } } } return res; } private TreeNode findPredecessor(TreeNode node) { TreeNode pred = node.left; while (pred.right != null && pred.right != node) { pred = pred.right; } return pred; } } ``` 这种做法的空间复杂度仅为 O(1)[^4],因为除了输入数据外无需任何附加内存支持。 --- ### 总结 以上介绍了三种不同的 Java 实现方案用于解决力扣上的二叉树中序遍历。每种方法各有优劣,在实际应用中可以根据需求灵活选用合适的策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值