代码随想录——二叉树

(一)理论基础

(1)种类:

  • 满二叉树 :节点数量为 2^k - 1

  • 完全二叉树:除了底层外,其它层都是满的,但是底层节点必须从左到右是连续的

  • 二叉搜索树:搜索的时间复杂度为logn级别,左子树所有节点都小于根节点,右子树所有节点都大于根节点

  • 平衡二叉搜索树:左子树和右子树高度差的绝对值不能超过1(map,set,multimap,multiset底层实现都是平衡二叉搜索树,是有序的;unordered_set,unordered_map底层实现为哈希表,是无序的)

(2)存储方式:

  • 链式存储:每个节点都包括左指针、右指针(更一般)

  • 线性存储:用字符数组保存二叉树(标号为每一层从左到右,自上而下进行数字标号)

                    a b c d e f g
    
                    0 1 2 3 4 5 6
    
                    元素 i 的左右孩子分别是2*i+1, 2*i + 2
    

(3)二叉树的遍历

  • 深度优先搜索:递归实现,迭代法也可以(前序/后序/中序遍历),一直搜到终点,回退,再换条路搜索

  • 前序/后序/中序遍历(中间节点的遍历顺序)

    前序遍历:-左-右

    中序遍历:左--右

    后序遍历:左-右-

    在这里插入图片描述

  • 广度优先搜索:一层一层遍历(层序遍历),迭代法,队列实现

(4)二叉树的定义

链表存储

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

(二)二叉树的递归遍历

前序/中序/后序遍历

递归三步分析:

step1: 确定递归函数的参数和返回值

参数:根节点、数组(存放遍历结果)

返回值:一般为void

step2: 确定终止条件

遇到空节点时,递归终止

step3: 确定单层递归的逻辑

前序:中-左-右(取出根节点,递归遍历左子树,递归遍历右子树)

中序:左-中-右(递归遍历左子树,取出根节点,递归遍历右子树)

后序:左-右-中(递归遍历左子树,递归遍历右子树,取出根节点)

//cur为根节点,vec存放遍历结果
void traversal(cur, vec){
  if(cur == NULL){ //终止条件
    return;
  }
  vec.push(cur -> val); //放入根节点
  traversal(cur -> left, vec); //遍历左子树
  traversal(cur -> right, vec); //遍历右子树
}
void traversal(cur, vec){
  if(cur == NULL){ //终止条件
    return;
  }
  traversal(cur -> left, vec); //遍历左子树
  vec.push(cur -> val);  //放入根节点
  traversal(cur -> right, vec); //遍历右子树
}
void traversal(cur, vec){
  if(cur == NULL){ //终止条件
    return;
  }
  traversal(cur -> left, vec); //遍历左子树
  traversal(cur -> right, vec); //遍历右子树
  vec.push(cur -> val);  //放入根节点
}
class Solution {
public:
    void traversal(TreeNode* root, vector<int>& vec){
        if(root == NULL){
            return;
        }
        vec.push_back(root -> val);
        traversal(root -> left, vec);
        traversal(root -> right, vec);
    }
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
    }
};

(三)二叉树的迭代遍历

使用栈实现非递归遍历

先处理中间节点,再将右节点入栈,再做节点(出栈顺序)

逻辑:

①前序遍历

step1: 放入根节点

step2: 弹出一个元素,将它的右节点、左节点先后放入

step3: 重复第二步,直到栈为空

void<int> function(root){
  stack<node> it;
  vector<int> vec; //存放遍历的结果
  it.push(root);
  while(!st.empty()){
    //1.取出一个节点
    node = st.top();
    st.pop();
    //2.判断取出的节点是否为空,不为空则放入结果数组(中)
    if(node != NULL) vec.push(node -> val)
    else continue;
    //3.将取出节点的右、左孩子分别入栈
    st.push(node -> right); //(右)
    st.push(node -> left); //(左)
  }
  return vec;
}
class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
       stack<TreeNode*> st;
       vector<int> result;
       st.push(root);
       TreeNode* temp;
       while(!st.empty()){
         temp = st.top();
         st.pop();
         if(temp != NULL){
            result.push_back(temp -> val);
         }else{
            continue;
         }
         st.push(temp -> right);
         st.push(temp -> left);
       }
       return result;
    }
};

②后序遍历

前序 :中-左-右 → 中-右-左左-中-右(后序)

后序 :左-右-中

void<int> function(root){
  stack<node> it;
  vector<int> vec; //存放遍历的结果
  it.push(root);
  while(!st.empty()){
    //1.取出一个节点
    node = st.top();
    st.pop();
    //2.判断取出的节点是否为空,不为空则放入结果数组(中)
    if(node != NULL) vec.push(node -> val)
    else continue;
    //3.将取出节点的右、左孩子分别入栈
    st.push(node -> left); //(左)
    st.push(node -> right); //(右)   
  }
  return vec.reverse; //反转数组
}

③中序遍历

访问节点、处理节点

对于前序遍历(中-左-右):访问顺序和处理(加入数组)顺序一致

中序(左-中-右),访问顺序和处理顺序不一致

一路向左,不断压栈,直到找到叶子节点为止,将其出栈,放入数组

将中间节点出栈,放入数组

将右孩子入栈……、

用指针遍历节点,用栈记录遍历过的节点,再按照顺序从栈中弹出元素放入数组

vector<int> tranversal(root){
  vector<int> result;
  stack<node*> st; //用栈来存储指针的位置
  node* cur = root;
  //注意这里是或的关系,就是栈为空同时指针指向e
  while(cur != NULL || !st.empty()){
    if(cur != NULL){
      st.push(cur); //栈用来记录指针访问过的元素
      cur = cur -> left;
    }else{
      cur = st.top();
      st.pop();
      result.push_back(cur -> val);
      cur = cur -> right;
    }
  }
  return result;
}
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        // 这里用栈记录指针访问过的位置
        stack<TreeNode*> st;
        vector<int> result;
        TreeNode* cur = root;
        // 满足一个即要继续遍历二叉树
        while(cur != NULL || !st.empty()){
            if(cur != NULL){
                st.push(cur);
                cur = cur -> left;
            }else{
                cur = st.top();
                st.pop();
                result.push_back(cur -> val);
                cur = cur -> right;
            }
        }
        return result;
    }
};

(四)二叉树的层序遍历

广度优先遍历:队列实现

记录当前层的大小size,控制每层从队列中弹出几个元素

当上一层元素全部弹出后,更新size大小,栈里面剩余元素数量就是当前层的元素数量。通过while循环控制将该层全部弹出。

queue<TreeNode*> que;
if(root != NULL) que.push(root);
result;//二维数组
while(!que.empty()){
  //上一层遍历完,记录当前层节点的个数
  size = que.size();
  vector<int> vec; // 记录当前层所有节点
  //弹出当前层的所有元素。
  //因为que.size是不断变化的,因此要先记录当前层所有节点数
  while(size--){
    node = que.front();
    que.pop();
    vec.push_back(node -> value);
    // 将出栈元素的左右节点入栈
    if(node -> left) que.push(node -> left);
    if(node -> right) que.push(node -> right);
  }
  result.push_back(vec); //将当前层节点放入二维数组
}
return result;
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        queue<TreeNode*> que;
        if(root != NULL) que.push(root);
        while(!que.empty()){
            int size = que.size();
            vector<int> vec;
            while(size --){
                TreeNode* top = que.front();
                que.pop();
                vec.push_back(top -> val);
                if(top -> left) que.push(top -> left);
                if(top -> right) que.push(top -> right);
            }
            result.push_back(vec);
        }
        return result;
    }
};

拓展:

①二叉树的最大深度

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root == NULL){
            return 0;
        }
        int depth = 0;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()){
            int size = que.size();
            depth ++;
            while(size --){
                TreeNode* node = que.front();
                que.pop();
                if(node -> left) que.push(node -> left);
                if(node -> right) que.push(node -> right);
            }
        }
        return depth;
    }
};

②二叉树的最小深度

class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root == NULL){
            return 0;
        }
        if(root -> left == NULL) return minDepth(root -> right) + 1;
        if(root -> right == NULL) return minDepth(root -> left) + 1;
        return min(minDepth(root -> left),minDepth(root -> right)) + 1;
    }
};
class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root == NULL){
            return 0;
        }
        int depth = 0;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()){
            depth ++;
            int size = que.size();
            while(size --){
                TreeNode* node = que.front();         
                que.pop();
                if(node -> left) que.push(node -> left);
                if(node -> right) que.push(node -> right);
                if(!node -> left && !node -> right) return depth;
            }            
        }
        return depth;
    }
};

(五)反转二叉树

交换指针,不是单纯交换值

前序较为方便

TreeNode* invertTree(root){
  if(root == NULL) return root;
  // 处理中间节点,交换其左右孩子指针
  swap(root -> left, root -> right);
  invertTree(root -> left);
  invertTree(root -> right);
}

不要使用中序遍历,较为麻烦。

左子树交换了之后,到了右子树,又处理一遍,而右子树没有处理。

(六)对称二叉树

左右子树能否相互反转

确定遍历方式:只能使用后序(左-右-中)

左边的孩子处理完了,右边的孩子处理完了,收集左右孩子的信息,向上面的节点返回结果。

终止条件

返回结果
不为空false
不为空false
true
不为空不为空(值不等)false
不为空不为空(值相等)继续向下一层递归遍历
//判断左右子树是否可以反转
bool compare(TreeNode* left, right){
  // 终止条件
  if(left == NULL && right != NULL) false;
  else if(left != NULL && right == NULL) false;
  else if(left == NULL && right == NULL) true;
  else if(left -> val != right -> val) false;
  // 处理单层递归逻辑,对左子树来说,是左-右-中处理逻辑,对应后序遍历
  // 比较左右子树的外侧节点是否相等
  bool outside = compare(left -> left, right -> right);  //(左)
  // 表左右子树的内次节点是否相等
  bool inside = compare(left -> right, right -> left);  //(右)
  bool result = outside && inside;   //(中)
  return result;
}
class Solution {
public:
    bool compare(TreeNode* left, TreeNode* right){
        if(left == NULL && right != NULL) return false;
        else if(left != NULL && right == NULL) return false;
        else if(left == NULL && right == NULL) return true;
        else if(left -> val != right -> val) return false;
        bool outside  = compare(left -> left, right -> right);
        bool inside = compare(left -> right, right -> left);
        bool result = outside && inside;
        return result;
    }
    bool isSymmetric(TreeNode* root) {
        return compare(root -> left, root -> right);
    }
};

(七)二叉树的最大深度

深度:二叉树中的任一节点到根节点的距离,根节点的深度为1**(前序遍历)**

      往下遍历一个就+1,符合二叉树的遍历过程,前序遍历

高度:二叉树中的任一节点到叶子节点的距离,叶子节点的高度为1**(后序遍历)**

     将叶子节点的高度返回给上面的父节点,父节点在这个基础上+1

根节点的最大高度即为最大深度

通过高度求深度(后序遍历)

int getheight(node){
  if(node == NULL) return 0;
  int leftheight = getheight(node -> left); 左
  int rightheight = getheight(node -> right); 右
  //中:处理逻辑,父节点在左右孩子结果的max基础上+1
  int height = 1 + max(leftheight, rightheight);
  // 根节点的深度,即为该二叉树的最大深度
  return height;
}
return 1 + max(getheight(node -> left), getheight(node -> right))
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root == NULL) return 0;
        queue<TreeNode*> que;
        que.push(root);
        int depth = 0;
        while(!que.empty()){
            int size = que.size();
            depth ++;
            while(size --){
                TreeNode* node = que.front();
                que.pop();
                if(node -> left) que.push(node -> left);
                if(node -> right) que.push(node -> right);
            }
        }
        return depth;
    }
};

(八)二叉树的最小深度

根节点到最近叶子节点的距离

求根节点的最小高度 → 二叉树的最小深度

使用后序遍历,注意什么是叶子节点!!

int getheight(TreeNode* node){
  if(node == NULL) return 0;
  int leftheight = getheight(node -> left); (左)
  int rightheight = getheight(node -> right);  (右)
  // 不能直接返回leftheight和rightheight的最小值
  // 特殊情况:左子树为空,右子树不为空,这时考虑的位右子树的最小高度,因为此时根节点不是叶子节点。而求最小深度计算的是到叶子节点的深度
  // 左子树不为空,右子树为空同上
  if(node -> left == NULL && node -> right != NULL) (中)
    return 1 + rightheight;
  if(node -> left != NULL && node  -> right == NULL)
    return 1 + leftheight;
  // 正常情况,左右子树都非空
  return 1 + min(leftheigth, rightheight);
}
class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root == NULL) return 0;
        int leftheight = minDepth(root -> left);
        int rightheight = minDepth(root -> right);
        if(root -> left == NULL && root -> right != NULL){
            return rightheight + 1;
        }else if(root -> left != NULL && root -> right == NULL){
            return leftheight + 1;
        }else{
            return min(leftheight, rightheight) + 1;
        }
    }
};
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root == NULL) return 0;
        queue<TreeNode*> que;
        que.push(root);
        int depth = 0;
        while(!que.empty()){
            int size = que.size();
            depth ++;
            while(size --){
                TreeNode* node = que.front();
                que.pop();
                if(node -> left) que.push(node -> left);
                if(node -> right) que.push(node -> right);
                if(!node -> right && !node -> left) return depth;
            }
        }
        return depth;
    }
};

(九)完全二叉树的节点个数

(1)当做普通二叉树

递归遍历/层序遍历都可

int getNum(node){
  if(node == NULL) return 0;
  leftNum = getNum(Node -> left); // 左子树节点数量
  rightNum = getNum(Node -> right); // 右子树节点数量
  result = leftNum + rightNum + 1; // (中)左子树节点数+右子树节点数+根节点1个
  return result;
}

(2)利用完全二叉树的特性

完全二叉树:除了底层节点,上面的节点都是满的,底层节点从左到右排列

除了最后一层,上面的为满二叉树,假设层数为n,则满二叉树的节点数为2^n - 1

如何判断一个子树为满二叉树:

如果从根节点一直向左遍历的深度=一直向右遍历的深度,则为满二叉树

(注意:因为这是一个完全二叉树,所以上面结论成立,最后一层中间不可能空)

如果该根节点对应的不是满二叉树,则继续向下遍历,判断下一个节点是否为满二叉树。叶子节点一定为二叉树,因此递归可以出来。

这样可以减少遍历的节点数

int getNum(node){
  //终止条件 ①空节点
  if(node == NULL) return 0;
  left = node -> left;
  right = node -> right;
  leftdepth = 0; rightdepth = 0;
  while(left){
    left = left -> left;
    leftdepth ++;
  }
  while(right){
    right = right -> right;
    rightdepth ++;
  }
  //终止条件 ②找到满二叉树,直接返回该子树的节点数
  if(leftdepth == rightdepth){
    // 注意这里的 2<<leftdepth,其实是2^(leftdepth+1) - 1
    // 这里层数是从0开始计数,计算的时候从1开始算,自动加1
    return (2 << leftdepth) -1;
  }
  // 单层递归逻辑
  leftNum = getNum(node -> left); //左
  rightNum = getNum(node -> right);  //右
  result = leftNum + rightNum + 1;
  return result;
}
class Solution {
public:
    int countNodes(TreeNode* root) {
        // if(root == NULL) return 0;
        // int leftNum = countNodes(root -> left);
        // int rightNum = countNodes(root -> right);
        // int result = leftNum + rightNum + 1;
        // return result;
        if(root == NULL) return 0;
        TreeNode* left = root -> left;
        TreeNode* right = root -> right;
        int leftDepth = 0;
        int rightDepth = 0;
        while(left){
            leftDepth ++;
            left = left -> left;
        }
        while(right){
            rightDepth ++;
            right = right -> right;
        }
        if(leftDepth == rightDepth){
            return (2 << leftDepth) - 1;
        }
        int leftNum = countNodes(root -> left);
        int rightNum = countNodes(root -> right);
        int result = leftNum + rightNum + 1;
        return result;
    }
};

(十)平衡二叉树

判断每个节点的左右子树的高度

采用后序遍历法

//求一个节点的高度,注意每一层递归调用多了一个异常判断-1
int getHeight(node){
  if(node == NULL) return 0; 
  leftHeight = getHeight(node -> left);  // 左
  // 如果左子树非平衡二叉树,将结果直接返回到最上层递归,退出递归
  if(leftHeight == -1) return -1;
  rightHeight = getHeight(node -> right);  // 右
  // 如果右子树非平衡二叉树,将结果直接返回到最上层递归,退出递归
  if(rightHeight == -1) return -1;
  int result;
  // 单层实现逻辑,如果对于根节点来说,左右子树高度差大于1,则返回-1
  if(abs(rightHeight - leftHeight) > 1) result = -1; // 中
  // 是平衡二叉树,则返回父节点的高度,为左右子树高度最大值加上本身的1
  else result = 1 + max(rightHeight, leftHeight); 
  return result;
}
class Solution {
public:
    int getHeight(TreeNode* root){
        if(root == NULL) return 0;
        int leftHeight = getHeight(root -> left);
        if(leftHeight == -1) return -1;
        int rightHeight = getHeight(root -> right);
        if(rightHeight == -1) return -1;
        if(abs(leftHeight - rightHeight) > 1) return -1;
        else return max(leftHeight,rightHeight) + 1;
    }
    bool isBalanced(TreeNode* root) {
        if(root == NULL) return true;
        if(getHeight(root -> left) == -1 || getHeight(root -> right) == -1)
           return false;
        if(abs(getHeight(root -> left) - getHeight(root -> right)) > 1)
           return false;
        else return true;
    }
};

(十一)二叉树的所有路径

前序遍历,让父节点不断指向孩子节点

递归法(回溯)

// path为一条路径,vector为结果集
void traversal(TreeNode* node, vector<int>& path, vector<string>& result){
  //由于是到叶子节点就结束了,所以要先收集节点再判断终止
  path.push_back(node -> val); //中
  //终止条件,遍历到叶子节点
  if(node -> left == NULL && node -> right == NULL){
    result.push_back(path); // 注意先将数组转为string
    return;
  }
  if(node -> left){
    //有递归就有回溯,递归和回溯放一起
    traversal(node -> left, path, result);
    path.pop_back(); //回溯
  }
  if(node -> right){
    traversal(node -> right, path, result);
    path.pop_back();
  }
}
class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& path, vector<string>& result){
        path.push_back(cur->val);
        if(cur -> left == NULL && cur -> right == NULL){
            string spath = "";
            for(int i = 0; i < path.size() - 1; i++){
                spath += to_string(path[i]);
                spath += "->";
            }
            spath += to_string(path[path.size() - 1]);
            result.push_back(spath);
            return;
        }
        if(cur -> left){
            traversal(cur -> left, path, result);
            path.pop_back();
        }
        if(cur -> right){
            traversal(cur -> right, path, result);
            path.pop_back();
        }
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        vector<int> path;
        if(root == NULL) return result;
        traversal(root, path, result);
        return result;
    }
};

补充:

将回溯隐藏,可以将参数改为 string path,不加上引用& ,即本层递归中,path + “→”+ 该节点数值,但该层递归结束,上一层path的数值并不会受到任何影响。 (值传递和地址传递的区别)

class Solution {
public:
    void traveral(TreeNode* cur, string path, vector<string>& result){
        path += to_string(cur -> val);
        if(cur -> left == NULL && cur -> right == NULL){
            result.push_back(path);
            return;
        }
        if(cur -> left) traveral(cur -> left, path + "->", result);
        if(cur -> right) traveral(cur -> right, path + "->", result);
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        string path;
        if(root == NULL) return result;
        traveral(root, path, result);
        return result;
    }
};

(十二)左叶子之和

(1)递归法

左叶子:叶子节点(左右孩子为空)

无法在遍历到叶子节点时判断该节点时左节点还是右节点,只有在遍历到父节点的时候判断其是否有左子树,并且左子树为叶子节点。

遍历顺序:后序遍历

收集左子树和右子树分别的左叶子节点之和,向父节点返回。

int tranversal(TreeNode node){
  if(node == NULL) return 0;
  if(node -> left == NULL && node -> right == NULL) return 0;
 // 后序遍历
  int leftNum = tranversal(node -> left); // 左
  // 收集左叶子节点的值
  if(node -> left != NULL && node -> left -> left == NULL && node -> left -> right == NULL)
   leftNum = node -> left -> val;
  int rightNum = tranversal(node -> right); // 右
  int sum = leftNum + rightNum; // 中
  return sum;
class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if(root == NULL) return 0;
        if(root -> left == NULL && root -> right == NULL) return 0;      
        int left = sumOfLeftLeaves(root -> left);
        // 这里不能return,因为当前节点右节点的右子树还没有遍历
        if(root -> left != NULL && root -> left -> left == NULL && root -> left -> right == NULL){
            left += root -> left ->val;
        }
        int right = sumOfLeftLeaves(root -> right);
        int sum = left + right;
        return sum;
    }
};

(2)迭代法

迭代法使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        stack<TreeNode*> st;
        if(root == NULL) return 0;
        st.push(root);
        int result = 0;
        while(!st.empty()){
            TreeNode* node = st.top();
            st.pop();
            if(node -> left != NULL && node -> left -> left == NULL && node -> left -> right == NULL){
                result += node -> left -> val;
            }
            if(node -> left) st.push(node -> left);
            if(node -> right) st.push(node -> right);
        }
        return result;
    }
};

(十三)找树左下角的值

(1)迭代法(较为简单)

只需要记录最后一行第一个节点的数值就可以了。

class Solution {
public:
    int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode*> que;
        if(root != NULL) que.push(root);
        int result = 0;
        while(!que.empty()){
            int size = que.size();
            result = que.front()-> val;
            for(int i = 0; i < size; i++){
                TreeNode* node = que.front();
                que.pop();
                if(node -> left) que.push(node -> left);
                if(node -> right) que.push(node -> right);
            }
        }
        return result;
    }
};

(2)递归法

深度最大的叶子节点,一定是最后一行

只要先遍历左节点就行,前序、中序、后序都可以

int maxDepth = INT_MIN; // 记录最大深度
int result; // 只要该节点的深度比最大深度大,都记录该节点的值
void traversal(root, depth){
  if(root -> left == NULL && root -> right == NULL){
    if(depth > maxDepth){
      maxDepth = depth;
      result = root -> val;
    }
  }
  // 左
  if(root -> left){
    // 递归加上回溯,维护depth的值
    depth ++;
    traversal(root -> left, depth);
    depth --;
    // 精简:traversal(root -> left, depth + 1); 这里并没有改变depth的值,隐藏了回溯的过程
  }
  // 右
  if(root -> right){
    depth ++;
    traversal(root -> right, depth);
    depth --;
  }
class Solution {
public:
    int maxDepth = INT_MIN;
    int result;
    void traversal(TreeNode* root, int depth){
        if(root -> left == NULL && root -> right == NULL){
            if(depth > maxDepth){
                maxDepth = depth;
                result = root -> val;
            }
            return;
        }
        if(root -> left){
            depth ++;
            traversal(root -> left, depth);
            depth --;
        }
        if(root -> right){
            depth ++;
            traversal(root -> right, depth);
            depth --;
        }
    }
    int findBottomLeftValue(TreeNode* root) {
        traversal(root, 0);
        return result;      
    }
};

(十四)路径总和

从根节点到叶子节点,路径上所有节点相加为目标和

前中后序遍历都可以

//传入目标sum,遇到一个节点将sum减去1,直到遇到叶子节点为止
bool traversal(TreeNode node, int count) {
    if(node -> left == NULL && node -> right == NULL && count == 0)
       return true;
    if(node -> left == NULL && node -> right == NULL && count != 0)
       return false;
    if(node -> left){ //左
       count -= node -> left -> val;
       return traversal(node -> left, count); //左方向是否有符合题目要求的路径,将结果继续向上返回
       count += node -> left -> val; //回溯,有减就有加
    }
    if(node -> right){ //右
       count -= node -> right -> val;
       return traversal(node -> right, count);
       count += node -> right -> val;
    }
  return false;
}
//传入目标sum,遇到一个节点将sum减去1,直到遇到叶子节点为止
bool traversal(TreeNode node, int count) {
    if(node -> left == NULL && node -> right == NULL && count == 0)
       return true;
    if(node -> left == NULL && node -> right == NULL && count != 0)
       return false;
    if(node -> left){ //左
       return traversal(node -> left, count - node -> left -> val);//只是参数改变了,count的值没有变,因此隐藏了回溯的过程
    }
    if(node -> right){ //右
       return traversal(node -> right, count - node -> right -> val);
    }
  return false;
}
class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) {
        if (!root) return false;
        if(!root->left && !root->right && sum != root->val){
            return false;
        }
        if (!root->left && !root->right && sum == root->val) {
            return true;
        }
        if(root -> left){
            sum -= root -> val;
            //是false的时候不能返回,因为这时候可能右子树还没遍历完,而只要有一个满足条件,即结果为true,可以直接返回
          //这里如果得到一个可能的结果,必须将true层层传递上去,否则结果被后面的false盖住,得不到正确结果
            if(hasPathSum(root -> left, sum)) return true;
            sum += root -> val;
        }
        if(root -> right){
             sum -= root -> val;
            if(hasPathSum(root -> right, sum)) return true;
            sum += root -> val;
        }
        return false;
    }
};

(十五)从中序与后序遍历序列构造二叉树

以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。

6步:

step1:如果后序数组大小为0,则返回空节点。

step2:如果后序数组长度不为0,则取后序数组最后一个元素作根节点元素。

step3:找到后序数组最后一个元素在中序数组的位置,作为切割点。

step4切割中序数组,切成中序左数组和中序右数组。

step5:根据中序数组的切割情况,切割后序数组为后序左数组和后序右数组。

step6:递归处理左区间和右区间。

TreeNode* traversal(inorder,preorder){
  if(preorder.size == 0) return null;
  rootvalue = preorder[preorder.size - 1]; //中间节点
  root = new TreeNode(rootvalue);
  if(preorder.size == 1) return root; //只有一个节点
  int index = 0; //切割位置(可以用来切中序和后序)
  for(index = 0; index < inorder.size;index ++){
    if(inorder[index] == rootvalue) break;
  }
  //切中序数组,左中序,右中序(左开右闭)
  vector<int> leftInorder(inorder.begin, inorder.begin + index);
  vector<int> rightInorder(inorder.begin + index + 1, inorder.end);
  //切后序数组(要丢弃最后一个元素),左后序,右后序(左开右闭)
   vector<int> leftInorder(inorder.begin, inorder.begin + index);
  vector<int> rightInorder(inorder.begin + index, inorder.end - 1);
  //递归处理左区间和右区间
  root -> left = traversal(左中序,左后序);
  root -> right = traversal(右后序,右中序);
  return root;                        
}
lass Solution {
public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if(inorder.size() == 0) return NULL;
        int rootvalue = postorder[postorder.size() - 1];
        TreeNode* root = new TreeNode(rootvalue);
        if(inorder.size() == 1) return root;
        int index;
        for(index = 0; index < inorder.size(); index ++){
            if(inorder[index] == rootvalue) break;
        }
        vector<int> leftInorder(inorder.begin(), inorder.begin() + index);
        vector<int> rightInorder(inorder.begin() + index + 1, inorder.end());
        vector<int> leftPostorder(postorder.begin(),postorder.begin() + index);
        vector<int> rightPostorder(postorder.begin() + index, postorder.end() - 1);
        root -> left = buildTree(leftInorder, leftPostorder);
        root -> right = buildTree(rightInorder, rightPostorder);
        return root;
    }
};

(补充)中序和前序可以确定唯一一棵二叉树

前序最前面一个元素确定根节点,其他同上

注:前序和后序不能确定一棵二叉树,因为这两种顺序左右子树是连在一起的,无法分割。

总结:必须要有中序才能确定

(十六)最大二叉树

前序遍历,先找到最大的元素,构造根节点,再构造左子树和右子树

(构造二叉树类的题目,用前序遍历,必须要先把根节点构造出来)

TreeNode* contruct(nums){
  if(num.size == 1) {//终止条件,到叶子节点,这里默认数组默认有1个元素(题目要求),这里影响了下面左右递归前的条件判断。
    return new TreeNode(nums[0]);
  }
  int maxValue = 0;//数组里面的最大值,用来构造根节点(数组里面所有数为正数)
  int index = 0;//数组最大值对应的下表,用来分割数组
  for(int i = 0; i < nums.size(); i++){
    if(nums[i] > maxValue){
      maxValue = nums[i];
      index = i;
    }
  }
  node = new TreeNode(maxValue);//中
  //分割左右子树,递归。但是要保证左右区间的大小要大于1,因为这里没有判断数组大小等于0的情况
  if(index > 0){
    newVec(0, index); //左闭右开
    node -> left = contruct(newVec);
  }
  if(index < nums.size() - 1){
    newVec(index + 1, nums.size());
    node -> right = construct(newVec);//左闭右开
  }
  return root;
}
class Solution {
public:
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        if(nums.size() == 1){
            return new TreeNode(nums[0]);
        }
        int maxValue = 0;
        int index = 0;
        for(int i = 0; i < nums.size(); i++){
            if(nums[i] > maxValue){
                maxValue = nums[i];
                index = i;
            }
        }
        TreeNode* root = new TreeNode(maxValue);
        if(index > 0){
          //注意这里必须是从nums.begin()开始,而不是从0开始
            vector<int> leftNums(nums.begin(), nums.begin() + index);
            root -> left = constructMaximumBinaryTree(leftNums);
        }
        if(index < nums.size() - 1){
            vector<int> rightNums(nums.begin() + index + 1, nums.end());
            root -> right = constructMaximumBinaryTree(rightNums);
        }
        return root;
    }
};

优化:不要每次都构造新的数组,每次传入开始和结束的下表即可。

(十七)合并二叉树

同时遍历两个二叉树(传入两个树的节点,同时操作)

(1)递归法

前中后序遍历都可以,可以另外构造一棵树,也可以直接修改t1的结构和值

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(root1 == NULL) return root2;
        if(root2 == NULL) return root1;
        TreeNode* t1 = new TreeNode();
        t1 -> val = root1 -> val + root2 -> val;
        t1 -> left = mergeTrees(root1 -> left, root2 -> left);
        t1 -> right = mergeTrees(root1 -> right, root2 -> right);
        return t1;
    }
};

(2)迭代法

模拟层序遍历,这里就不需要统计到了第几层了,只要每次取出两个节点,对应配对的两个子树的节点处理了就行。

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if(root1 == NULL) return root2;
        if(root2 == NULL) return root1;
        queue<TreeNode*> que;
        que.push(root1);
        que.push(root2);
        while(!que.empty()){
            TreeNode* node1 = que.front(); que.pop();
            TreeNode* node2 = que.front(); que.pop();
            node1->val = node1->val + node2->val;
            if(node1->left != NULL && node2->left != NULL){
                que.push(node1->left);
                que.push(node2->left);
            }
            if(node1->right != NULL && node2->right != NULL){
                que.push(node1->right);
                que.push(node2 -> right);
            }
            if(node1->left == NULL && node2->left != NULL){
                node1 -> left = node2 -> left;
            }
            if(node1->right == NULL && node2->right != NULL){
                node1 -> right = node2 -> right;
            }
        }
        return root1;
    }
};

(十八)二叉搜索树中的搜索

二叉搜索树是一个有序数:

若其左子树不空,则左子树上所有节点的值均小于它的根节点的值。

若其右子树不空,则右子树上所有节点的值均大于它的根节点的值。

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        //这里要把root == NULL 写在前面,确保后面的root -> val有意义
        if(root == NULL || root -> val == val ) return root;
        TreeNode* tree = NULL;
        if(root -> val > val)  tree = searchBST(root -> left, val);
        if(root -> val < val)  tree = searchBST(root -> right, val);
        return tree;
    }
};

(十九)验证二叉搜索树

判断一个二叉树是否为有效二叉树

在这里插入图片描述

(1)整理转化为数组进行判断

关键:中序遍历下,输出的二叉树节点的数值是有序序列

转化为:判断中序遍历二叉树生成的数组是否是递增的

注意:数组里面也不能有相同的元素

class Solution {
public:
    //注意这个不能再递归函数里面定义,因为每次递归会刷新他
    vector<int> result;
    bool isValidBST(TreeNode* root) {
        if(root == NULL) return true;
        isValidBST(root -> left);
        result.push_back(root -> val);
        isValidBST(root -> right);
        for(int i = 1; i < result.size(); i ++){
            if(result[i] <= result[i - 1]){
                return false;
            }          
        }
        return true;
    }
};

(2)一边遍历一边判断

误区:根节点的左子树中所有节点都必须小于根节点,右子树中所有节点都必须大学根节点

因此不能只判断一个左孩子和一个右孩子

long long maxVal = Long_MIN;//用这个全局变量确定节点是有序的,因为这里节点的值可能出现int的最小值,为了比这个值更小,只能用Long long型变量 
bool isvalid(TreeNode* root){
  if(root == NULL) return true;
  bool left = isvalid(root -> left); //左
  //在中序遍历的过程中,节点值一直是增大的,因此这个maxValue其实是记录的当前节点的前一个遍历到的节点的值
  if(root -> value > maxValue){ //中
    maxValue = root -> val;
  }else{
    return false;
  }
  bool right = isValid(root -> right);//右
  return left && right;
}

可以不定义这个maxValue临时变量,而用双指针法,直接进行前一个节点和后一个节点比较

TreeNode* pre = NULL;
bool isvalid(TreeNode* root){
  if(root == NULL) return true;
  bool left = isvalid(root -> left); //左
  //pre!=NULL,跳过第一个节点
  if(pre != NULL && pre -> val >= root -> val){ //中
    return false;
  }
  pre = root;
  bool right = isValid(root -> right);//右
  return left && right;
}

(二十)二叉搜索树的最小绝对差

在二叉树搜索树上求最值、差值 → 在有序数组上求最值、差值(二叉搜索树的有序性)

(1)转为有序数组

class Solution {
private:
   vector<int> vec;
   void traversal(TreeNode* root){
    if(root == NULL) return;
    traversal(root -> left);
    vec.push_back(root -> val);
    traversal(root -> right);
   }
public:
    int getMinimumDifference(TreeNode* root) {
        traversal(root);
        if(vec.size() < 2) return true;
        int result = INT_MAX;
        for(int i = 1; i < vec.size(); i++){
            result = min(result, vec[i] - vec[i - 1]);
        }
        return result;
    }
};

(2)一边遍历一边比较

class Solution {
private:
   TreeNode* pre = NULL;
   int result = INT_MAX;
   void traversal(TreeNode* root){
    if(root == NULL) return;
    traversal(root -> left);
    if(pre != NULL){
        result = min(result, root -> val - pre -> val);
    }
    pre = root;
    traversal(root -> right);  
   }
public:
    int getMinimumDifference(TreeNode* root) {
        traversal(root);
        return result;
    }
};

(二十一)二叉搜索树中的众数

有相同值的二叉搜索树

在这里插入图片描述

找出所有众数

(1)当做非二叉搜索树来做

将二叉树遍历,用Map统计每种元素出现的频率,再对统计出来的频率排序

class Solution {
private:
    void searchBST(TreeNode* root, unordered_map<int, int>& map){
        if(root == NULL) return;
        map[root -> val] ++;
        searchBST(root -> left, map);
        searchBST(root -> right, map);
    }
    bool static cmp(const pair<int,int>& a, const pair<int, int>& b){
        return a.second > b.second; //从大到小排序
    }
public:
    vector<int> findMode(TreeNode* root) {
        unordered_map<int, int> map;
        vector<int> result;
        searchBST(root, map);
        vector<pair<int, int>> vec(map.begin(),map.end());
        sort(vec.begin(), vec.end(), cmp);
        result.push_back(vec[0].first);
        for(int i = 1; i < vec.size(); i++){
            if(vec[i].second == vec[0].second){
                result.push_back(vec[i].first);
            }
        }
        return result;
    }
};

(2)当做二叉搜索树来做

中序遍历,相同的元素都是临近的,用count来计数重复次数,用maxCount来保存重复次数最多的。

class Solution {
private:
    vector<int> vec;
    TreeNode* pre = NULL;
    int count;
    int maxCount = INT_MIN;
    void traversal(TreeNode* root){
        if(root == NULL) return;
        traversal(root -> left); //左
        if(pre == NULL){  //中
            count = 1;
        }else{
            if(root -> val == pre -> val)
               count ++;
            else
               count = 1;
        }
        pre = root;
        if(count == maxCount){//之前找到的不是出现频率最高的众数
            vec.push_back(root -> val);
        }
        if(count > maxCount){
            maxCount = count;
            vec.clear(); //这里注意要清空之前数组中的内容
            vec.push_back(root -> val);
        }
        traversal(root -> right); //右
    }

public:
    vector<int> findMode(TreeNode* root) {
        vec.clear();
        traversal(root);
        return vec;
    }
};

(二十二)二叉树的最近公共祖先

最近公共祖先节点可以为节点本身

回溯:从低往上遍历

后序遍历:左-右-中

情况1:左右子树中是否出现了p或q,中间节点负责向上返回

情况2:p和q某个出现在中间节点(在处理情况1的同时也处理情况2)

TreeNode* traversal(root, p, q){
  if(root == NULL) return NULL;
  if(root == p || root == q) return root;
  TreeNode* left = traversal(root -> left, p , q);//左
  TreeNode* right = traversal(root -> right, p, q);//右
  if(left != NULL && right != NULL) return root; //找到最近公共祖先
  if(left == NULL && right != NULL) return right; //结果往上逐层返回
  if(left != NULL && right == NULL) return left;
  else return NULL;//左右都不包含目标节点
}
class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL) return NULL;
        if(root == p || root == q) return root;
        TreeNode* left = lowestCommonAncestor(root -> left, p, q);
        TreeNode* right = lowestCommonAncestor(root -> right, p, q);
        if(left && right) return root;
        if(left && !right) return left;
        if(!left && right) return right;
        return NULL;
    }
};

(二十三)二叉搜索树的最近公共祖先

只要从上到下去遍历,遇到 cur节点是数值在[p, q]区间中则一定可以说明该节点cur就是p 和 q的公共祖先,可以证明这个也是最近公共祖先

(1)递归法

(相当于变形的前序遍历,只是中是条件判断而已)

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == NULL) return NULL;
        if(root -> val < p -> val && root -> val < q -> val){
            return lowestCommonAncestor(root -> right, p, q);
        }else if(root -> val > p -> val && root -> val > q -> val){
            return lowestCommonAncestor(root -> left, p, q);
        }else{
            return root;
        }
    }
};

(2)迭代法

这里由于二叉搜索树的有序性,不需要回溯,路径有顺序规定好

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while(root){
            if(root -> val < p -> val && root -> val < q -> val){
                root =  root -> right;
            }else if(root -> val > p -> val && root -> val > q -> val){
                root = root -> left;
            }else{
                return root;
            }
        }
        return NULL;
    }
};

(二十四)二叉搜索树中的插入操作

找到空节点插入元素,不需要调整树的结构

(1)递归法

有返回值,可以利用返回值完成新加入的节点与其父节点的赋值操作

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if(root == NULL){
            TreeNode* newNode = new TreeNode(val);
            return newNode;
        }
        if(root -> val < val){
            root -> right = insertIntoBST(root -> right, val);
        }
        if(root -> val > val){
            root -> left = insertIntoBST(root -> left, val);
        }
        return root;
    }
};

(2)迭代法

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
       if(root == NULL){
        TreeNode* node = new TreeNode(val);
        return node;
       }
       TreeNode* pre = root;// 记录最后一个找到的节点,否则无法将新节点连接到原二叉树上
       TreeNode* firstRoot = root;//记录头结点,最后返回更新后的整个树
       while(root){
         if(root -> val < val){
            pre = root; //pre记录上一个节点
            root = root -> right;
         }else if(root -> val > val){ //注意这里必须写else,递归法可能没影响,但是迭代法影响
            pre = root;
            root = root -> left;
         }
       }
       TreeNode* node = new TreeNode(val);
       if(pre -> val < val) pre -> right = node;
       if(pre -> val > val) pre -> left = node;
       return firstRoot;
    }
};

(二十五)删除二叉搜索树中的节点

删除节点→改变二叉树的结构

要符合二叉搜索树的结果

情况一:没找到要删的节点

情况二:找到,要删除的为叶子节点(最简单)

情况三:需要删除的节点左不空,右为空,让父节点直接指向左孩子

情况四:需要删除的节点左为空,右不空,让父节点直接指向右孩子

情况五:需要删除的节点左右孩子都不为空,右子树替补,左子树挂到右子树的最左叶子节点的左边(最复杂)

注意在C++中要手动释放内存

TreeNode* delete(root, key){//返回删掉目标节点后新的二叉树的根节点
  //不需要遍历整个二叉树,找到要删除的节点,执行删除逻辑,就退出遍历
  if(root == NULL) return NULL;// 没有找到删除节点
  if(root -> val == key){
    if(root -> left == NULL && root -> right == NULL){//情况二
      return NULL;
    }else if(root -> left != NULL && root -> right == NULL){//情况三
      return root -> left;
    }else if(root -> left == NULL && root -> right != NULL){//情况四
      return root -> right;
    }else{ //情况五
      cur = root -> right;
      while(cur -> left){
        cur = cur -> left; //找到右子树的最左边节点
      }
      cur -> left = root -> left; //要删的节点的左子树挂到cur上
      return root -> right; //用右子树替掉当前节点
    }
  }
  if(key < root -> val)//根据二叉搜索树的特性,决定搜索左子树还是右子树
    root -> left = delete(root -> left, key);
  if(key > root -> val)
    root -> right = delete(root -> right, key);
  return root;
}
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(root == NULL) return NULL;
        if(root -> val == key){
            if(root -> left == NULL && root -> right == NULL){
                delete root;
                return NULL;
            }else if(root -> left == NULL && root -> right != NULL){
                return root -> right;
            }else if(root -> left != NULL && root -> right == NULL){
                return root -> left;
            }else{
                TreeNode* cur = root -> right;
                while(cur -> left){
                    cur = cur -> left;
                }
                cur -> left = root -> left;
                return root -> right;
            }
        }
        if(root -> val > key){
            root -> left = deleteNode(root -> left, key);
        }
        if(root -> val < key){
            root -> right = deleteNode(root -> right, key);
        }
        return root;
    }
};

(二十六)修剪二叉搜索树

给定范围修剪二叉搜索树

不是当前节点的val不在范围内就return NULL!!! 因为可能他的 左/右子树里面有节点在范围内

当前val小于左边界,右子树中节点可能符合范围

当前val大于右边界,左子树中节点可能符合范围

TreeNode* traversal(root, low, high){
  if(root == NULL) return NULL;
  if(root -> val < low) {
    right = traversal(root -> right, low, high); //因为右子树中可能也有不符合范围的,因此要在这个规则下继续遍历右子树,返回修剪后的右子树根节点,不能直接返回右子树
    return right;
  }
   if(root -> val > high){
     left = traversal(root -> left, low, high);
     return left;
   }
  //这里是左右子树都要修剪
  root -> left = traversal(root -> left, low, high);
  root -> right = traversal(root -> right, low, high);
  return root;
}
class Solution {
public:
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if(root == NULL) return NULL;
        if(root -> val < low){
            return trimBST(root -> right, low, high);
        }
        if(root -> val > high){
            return trimBST(root -> left, low, high);
        }
        root -> left = trimBST(root -> left, low, high);
        root -> right = trimBST(root -> right, low, high);
        return root;
    }
};

(二十七)将有序数组转换为二叉搜索树

构造平衡二叉搜索树

每次都选取数组中间的,构造根节点,再递归构造左右子树

数组长度为奇数:取中间的数

数组长度为偶数:取靠右侧/靠左侧作为根节点(都可以)

TreeNode* traversal(vector<int>& nums, left, right){
  //设置为左闭右闭区间,当Left==right时,还是一个合法区间
  if(left > right) return NULL;
  int mid = (left + right) / 2;
  TreeNode* root = new TreeNode(nums[mid]);
  root -> left = traversal(nums, left, mid - 1);
  root -> right = traversal(nums, mid + 1, right);
  return root;
}
class Solution {
private:
    TreeNode* traversal(vector<int>& nums, int left, int right){
        if(left > right) return NULL;
        int mid = (left + right)/2;
        TreeNode* root = new TreeNode(nums[mid]);
        root -> left = traversal(nums, left, mid - 1);
        root -> right = traversal(nums, mid + 1, right);
        return root;
    }
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        TreeNode* root = traversal(nums, 0, nums.size() - 1);
        return root;
    }
};

(二十八)把二叉搜索树转换为累加树

每个节点node的新值等于原树中大于或等于node.val的值之和

相加的顺序:从最大的节点开始遍历相加,右-中-左(反中序遍历),从大到小遍历

双指针操作,cur指向当前操作的节点,pre记录上一个节点,就是需要累加到本节点的值

(本题是迭代法的一个模板题)

int pre = 0; //代表前一个节点的值,一开始为0,如果定义为TreeNode,可能会有空指针异常
void traversal(TreeNode* cur){//传入二叉搜索树的根节点
  if(cur == NULL) return;
  traversal(cur -> right); //右
  cur -> val += pre; //中
  pre = cur -> val; //pre移动
  traversal(cur -> left);//左
}
class Solution {
private:
    int pre = 0;
    void traversal(TreeNode* cur){
        if(cur == NULL) return;
        traversal(cur -> right);
        cur -> val += pre;
        pre = cur -> val;
        traversal(cur -> left);
    }
public:
    TreeNode* convertBST(TreeNode* root) {
        traversal(root);
        return root;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值