力扣HOT100(32)二叉树的中序遍历

方法一:递归法(最基础,面试先写这个)

核心思路

中序遍历天然具有递归性质:

  • 对于任意一个节点,先遍历它的左子树
  • 然后访问这个节点本身
  • 最后遍历它的右子树
  • 遇到空节点就直接返回(递归终止条件)

递归的本质是系统帮我们维护了一个调用栈,自动记录我们走到了哪里,处理完子树后自动回到父节点

cpp 复制代码
/**
 * Definition for a binary tree node.
 * 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:



    void inorder(TreeNode* root,vector<int>&res){
        if(!root){return;}
       
        inorder(root->left,res);
        //因为是中序 所以如果有左先存左
        res.push_back(root->val);
        inorder(root->right,res);
    }
    vector<int> inorderTraversal(TreeNode* root) {
        //法一 递归法 写一个函数

        vector<int> res;
        inorder(root,res);
        return res;

    }
};

方法二:迭代法(面试进阶要求,必须掌握)

1. 核心思路

递归和迭代是完全等价的,区别只是:

  • 递归:系统隐式地帮我们维护了一个调用栈
  • 迭代:我们自己显式地用一个栈来模拟这个过程

迭代法的核心逻辑(和递归完全一致):

  1. 先一路往左走,把所有经过的节点都压入栈(相当于递归里的 "先处理左子树")
  2. 走到头(左孩子为空)的时候,弹出栈顶节点(这个就是当前最左的节点,也就是中序的第一个)
  3. 访问这个节点(把值加入结果)
  4. 然后去处理这个节点的右子树(相当于递归里的 "最后处理右子树")
  5. 重复上面的步骤,直到栈为空且当前节点为空
cpp 复制代码
/**
 * Definition for a binary tree node.
 * 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<int> inorderTraversal(TreeNode* root) {
        //法二 迭代法 利用栈
        vector<int> res;
        stack<TreeNode*> stk;//定义一个栈
        while(root!=nullptr || !stk.empty()){
            //先找左边 把左边的都给找到头
            while(root!=nullptr){
                //遍历一个压一个入栈
                stk.push(root);
                root = root->left;
            }

            //root是空 说明到头了 此刻栈顶就是我们要第一个输出的 所以栈输出
            root = stk.top();
            stk.pop();;//清空栈
            res.push_back(root->val);
            root = root->right;
        }
        return res;
    }
};

方法三:Morris 中序遍历(面试加分项,O (1) 空间)

1. 核心思路

前面两种方法都需要 O (n) 的空间(递归栈或手动栈),Morris 遍历的牛逼之处在于:它利用了二叉树中大量空闲的右指针(叶子节点的右指针都是 nullptr),用这些指针来记录 "后继节点",从而不需要额外的栈空间,空间复杂度降到 O (1)。

核心思想: 对于当前节点 x:

  1. 如果 x 没有左孩子:直接访问 x,然后去 x 的右孩子
  2. 如果 x 有左孩子:
    • 找到 x 左子树中最右边的节点(这个节点是左子树中序遍历的最后一个节点,也就是 x 在中序遍历中的前驱节点),记为 predecessor

    • 如果 predecessor 的右指针是空:把它指向 x(相当于 "留个记号,等会儿遍历完左子树,从这里回到 x"),然后去 x 的左孩子

    • 如果 predecessor 的右指针已经指向 x 了:说明左子树已经遍历完了,把 predecessor 的右指针恢复为空(不能修改原树的结构),然后访问 x,再去 x 的右孩子

      cpp 复制代码
      /**
       * Definition for a binary tree node.
       * 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<int> inorderTraversal(TreeNode* root) {
              //法二 Morris 法 利用当前节点的前驱节点的右指针当做栈的作用 记录根节点
              vector<int> res;
              TreeNode *prede  = nullptr;
      
              while(root!= nullptr){
                  if(root->left != nullptr){
                      //当前节点有左孩子 那么 predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
                      prede  = root->left;
                      while(prede->right!=nullptr&&prede->right != root){
                          //直到 这个前驱节点的右指针为空并且右指针不等于root
      
                          prede = prede->right;
                      }
                      
                  //如果为空 那么就让这个指针指向根节点
                  if(prede->right == nullptr){
                      prede->right  = root;
                      root = root->left;
                  }
                  else{
                      //否则的话 说明程序已经不是第一次来了 这一次又遍历到头了
                      //那就重新清空prede 让它等于新的根节点。
                      res.push_back(root->val);
                      prede->right = nullptr;
                      root = root->right;
                  } 
                  }
                  else{
                      res.push_back(root->val);
                      root = root->right;
                  }
      
                  
              }
              return res;
          }
      };
相关推荐
Matlab程序猿8 分钟前
【MATLAB源码-第459期】基于matlab的D-H模型与阻尼最小二乘逆运动学的六自由度机械臂五次轨迹规划及动力学前馈PD控制仿真。
人工智能·算法·matlab·d-h建模·六自由度串联机械臂
信奥卷王10 分钟前
[GESP202606 六级] 满二叉树
数据结构·算法
积流成海19 分钟前
用代码实现哈希计算器:Web Crypto API 实战
前端·算法·哈希算法·哈希函数·盘子工具站
Nil20828 分钟前
leetcode 300最长递增子序列
算法·leetcode·职场和发展
垆边人似月.1 小时前
华为机试题 :最大数
c++·算法·华为
念越2 小时前
题库、练习与错题本代码阅读
开发语言·数据结构
zhenaibo5212 小时前
如何与导师有效沟通选题?
算法
熊猫钓鱼>_>2 小时前
大学图论第 6 讲:网络流与二分图匹配——对偶性让瓶颈自己现形
开发语言·算法·面试·职场和发展·图论·校招·面经
没有钱的钱仔2 小时前
最大似然估计(MLE)通俗理解
算法·机器学习·概率论
yi0112 小时前
LeetCode 424. 替换后的最长重复字符|不定长滑动窗口:如何判断窗口是否合法?
笔记·python·算法·leetcode·双指针·滑动窗口