leetcode刷题二叉树知识笔记

2022-08-09

leetcode刷题二叉树知识笔记。

二叉树遍历和构建二叉树

#include <iostream>
#include <vector>

#include <queue>

using namespace std;

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


TreeNode* construct_binary_tree(const vector<int>& vec){
    vector<TreeNode*> vecTree (vec.size(), NULL);
    TreeNode* root = NULL;
    // value 插入treenode节点
    for (int i = 0; i < vec.size(); i++){
        TreeNode* node = NULL;
        if (vec[i] != -1) node = new TreeNode(vec[i]);
        vecTree[i] = node;
        if (i == 0) root = node;
    }
    // treenode的left 和 right 分别指明指向
    for( int i = 0; i * 2 + 2 < vec.size(); i++){
        if (vecTree[i] != NULL){
            vecTree[i]->left = vecTree[i * 2 + 1];
            vecTree[i]->right = vecTree[i * 2 + 2];
        }
    }
    return root;
}

// 层次遍历打印

void print_binary_tree(TreeNode* root){
    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();
            if (node != NULL){
                vec.push_back(node->val);
                que.push(node->left);
                que.push(node->right);
            }
            else {
                vec.push_back(-1);
            }
        }
        result.push_back(vec);
    }
    for (auto i : result){
        for (auto j : i){
            cout << j << " ";
        }
        cout << endl;
    }
}

// 前序遍历迭代法
vector<int> preorderTraversal(TreeNode* root){
    stack<TreeNode* > st;
    vector<int> result;
    if (root == NULL) return result;
    st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top();
        st.pop();
        result.push_back(node->val);
        if (node->right) st.push(node->right);
        if (node->left) st.push(node->left);
    }
    return result;
}

// 中序遍历迭代法
vector<int> inorderTraversal(TreeNode* root){
    vector<int> result;
    stack<TreeNode* > st;
    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;
}

// 后续遍历迭代法
vector<int> preorderTraversal(TreeNode* root){
    stack<TreeNode* > st;
    vector<int> result;
    if (root == NULL) return result;
    st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top();
        st.pop();
        result.push_back(node->val);
        if (node->left) st.push(node->left);
        if (node->right) st.push(node->right);
    }
    reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
}

vector<int> preorderTraversal(TreeNode* root) {
        vector<int> result;
        traversal(root, result);
        return result;
}

// 递归法
void traversal(TreeNode* cur, vector<int>& vec) {
    if (cur == NULL) return;
    // 交换下面的顺序 分别对应 左中右 遍历
    traversal(cur->left, vec);  // 左
    vec.push_back(cur->val);    // 中
    traversal(cur->right, vec); // 右
}

// 迭代法的统一

vector<int> inorderTraversal(TreeNode* root) {
    vector<int> result;
    stack<TreeNode*> st;
    if (root != NULL) st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top();
        if (node != NULL) {
            st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
            if (node->right) st.push(node->right);  // 添加右节点(空节点不入栈)
            st.push(node);                          // 添加中节点

            st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。

            if (node->left) st.push(node->left);    // 添加左节点(空节点不入栈)
        } else { // 只有遇到空节点的时候,才将下一个节点放进结果集
            st.pop();           // 将空节点弹出
            node = st.top();    // 重新取出栈中元素
            st.pop();
            result.push_back(node->val); // 加入到结果集
        }
    }
    
    return result;
}


int main() {
    vector<int> vec = {4,1,6,0,2,5,7,-1,-1,-1,3,-1,-1,-1,8};
    TreeNode* root = construct_binary_tree(vec);
    print_binary_tree(root);
}

二叉树解题思路

二叉树解题的思维模式分两类:

  • 1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。

  • 2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。

无论使用哪种思维模式,你都需要思考:

如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点不用你操心,递归函数会帮你在所有节点上执行相同的操作。

所谓前序位置,就是刚进入一个节点(元素)的时候,后序位置就是即将离开一个节点(元素)的时候,那么进一步,你把代码写在不同位置,代码执行的时机也不同:

前中后序是遍历二叉树过程中处理每一个节点的三个特殊时间点,绝不仅仅是三个顺序不同的 List:

前序位置的代码在刚刚进入一个二叉树节点的时候执行;

后序位置的代码在将要离开一个二叉树节点的时候执行;

中序位置的代码在一个二叉树节点左子树都遍历完,即将开始遍历右子树的时候执行。

例题1

leetcode[104] 二叉树的最大深度。给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

解题思路:

  • 遍历的思路:遍历一遍二叉树,用一个外部变量记录每个节点所在的深度,取最大值就可以得到最大深度。
  • 二叉树的最大深度可以通过子树的最大深度推导出来。确实可以通过子树的最大深度推导出原树的深度,所以当然要首先利用递归函数的定义算出左右子树的最大深度,然后推出原树的最大深度,主要逻辑自然放在后序位置。

1.

// 记录最大深度
int res = 0;
// 记录遍历到的节点的深度
int depth = 0;

// 主函数
int maxDepth(TreeNode root) {
	traverse(root);
	return res;
}

// 二叉树遍历框架
void traverse(TreeNode root) {
	if (root == null) {
		return;
	}
	// 前序位置
	depth++;
    if (root.left == null && root.right == null) {
        // 到达叶子节点,更新最大深度
		res = Math.max(res, depth);
    }
	traverse(root.left);
	traverse(root.right);
	// 后序位置
	depth--;
}

2.

// 定义:输入根节点,返回这棵二叉树的最大深度
int maxDepth(TreeNode root) {
	if (root == null) {
		return 0;
	}
	// 利用定义,计算左右子树的最大深度
	int leftMax = maxDepth(root.left);
	int rightMax = maxDepth(root.right);
	// 整棵树的最大深度等于左右子树的最大深度取最大值,
    // 然后再加上根节点自己
	int res = Math.max(leftMax, rightMax) + 1;

	return res;
}