LeetCode高频题刷题笔记(八)二叉树

基础知识

前序遍历: 先遍历根节点,然后再分别遍历左节点和右节点。(根左右)
中序遍历: 先遍历左节点,然后再遍历根节点,最后遍历右节点。(左根右
后序遍历: 先遍历左节点,然后再遍历右节点,最后遍历根节点。(左右根
层次遍历:一层一层从左至右遍历数据。

1.递归版代码

前序遍历代码(C++):

void preOrder(TreeNode* root, vector<int>& res)
{
	if (root != nullptr)
	{
		res.push_back(root->val); // 处理节点
		preOrder(root->leftchild);
		preOrder(root->rightchild);
	}
}

中序遍历代码(C++):

void inOrder(TreeNode* root, vector<int>& res)
{
	if (root != nullptr)
	{
		inOrder(root->leftchild);
		res.push_back(root->val); // 处理节点
		inOrder(root->rightchild);
	}
}

后序遍历代码(C++):

void postOrder(TreeNode* root, vector<int>& res)  
{
	if (root != nullptr)
	{
		postOrder(root->leftchild);
		postOrder(root->rightchild);
		res.push_back(root->val); // 处理节点
	}
}

2.迭代版代码

前序遍历代码(C++):

vector<int> preOrder(TreeNode* root)
{
	vector<int> res;
    if (!root) return res;
    stack<TreeNode*> stk;
    stk.push(root);
    while (!stk.empty()) {
        TreeNode* node = stk.top();
        stk.pop();
        res.push_back(node->val); // 处理节点
        if (node->right) stk.push(node->right);
        if (node->left) stk.push(node->left);
    }
    return res;
}

中序遍历代码(C++):

vector<int> inOrder(TreeNode* root)
{
	stack<TreeNode*> stk;
    vector<int> res;
    while (root || !stk.empty()) {
        if (root) {
        	stk.push(root);
            root = root->left;
        } else {
            root = stk.top();
            stk.pop();
            res.push_back(root->val); // 处理节点
            root = root->right;
        }
    }
    return res;
}

后序遍历代码(C++):

vector<int> postOrder(TreeNode* root)   
{
	vector<int> res;
    if (!root) return res;
    stack<TreeNode*> stk;
    stk.push(root);
    while (!stk.empty()) {
        TreeNode* node = stk.top();
        stk.pop();
        res.push_back(node->val); // 处理节点
        if (node->left) stk.push(node->left);
        if (node->right) stk.push(node->right);
    }
    reverse(res.begin(), res.end());
    return res;
}

层序遍历代码(C++):

vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    if (!root) return res;
    queue<TreeNode*> que;
    que.push(root);
    vector<int> tmp;
    while (!que.empty()) {
        tmp.clear();
        int size = que.size();
        while (size > 0) {
            TreeNode* node = que.front();
            que.pop();
            tmp.push_back(node->val); // 处理节点
            if (node->left) que.push(node->left);
            if (node->right) que.push(node->right);
            size--;
        }
        res.push_back(tmp);
    }
    return res;
}

3.Morris版代码

前序遍历代码(C++):

vector<int> preOrder(TreeNode* root)
{
	vector<int> res;
    TreeNode *p1 = root, *p2 = nullptr;
    while (p1) {
        if (p1->left) {
            p2 = p1->left;
            while (p2->right && p2->right != p1) {
                p2 = p2->right;
            }
            if (p2->right == nullptr) {
                res.push_back(p1->val);
                p2->right = p1;
                p1 = p1->left;
                continue;
            } 
            p2->right = nullptr;
        } else {
            res.push_back(p1->val);
        }
        p1 = p1->right;
    }
    return res;
}

中序遍历代码(C++):

vector<int> inOrder(TreeNode* root)
{
	vector<int> res;
    TreeNode *p1 = root, *p2 = nullptr;
    while (p1) {
        if (p1->left) {
            p2 = p1->left;
            while (p2->right && p2->right != p1) {
                p2 = p2->right;
            }
            if (p2->right == nullptr) {
                p2->right = p1;
                p1 = p1->left;
                continue;
            }
            p2->right = nullptr;
            res.push_back(p1->val);
        } else {
            res.push_back(p1->val);
        }
        p1 = p1->right;
    }
    return res;
}

后序遍历代码(C++):

void addPath(vector<int> &vec, TreeNode *root) {
    int count = 0;
    while (root != nullptr) {
        ++count;
        vec.push_back(root->val);
        root = root->right;
    }
    reverse(vec.end() - count, vec.end());
}
vector<int> postOrder(TreeNode *root) {
    vector<int> res;
    TreeNode *p1 = root, *p2 = nullptr;
    while (p1) {
        if (p1->left) {
            p2 = p1->left;
            while (p2->right && p2->right != p1) {
                p2 = p2->right;
            }
            if (p2->right == nullptr) {
                p2->right = p1;
                p1 = p1->left;
                continue;
            }
            p2->right = nullptr;
            addPath(res, p1->left);
        }
        p1 = p1->right;
    }
    addPath(res, root);
    return res;
}

题目

1.翻转二叉树( LeetCode 226 )

难度: 简单
题目表述:
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
代码(C++):

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == nullptr) return root;
        TreeNode* left = invertTree(root->left);
        TreeNode* right = invertTree(root->right);
        root->left = right;
        root->right = left;
        return root;
    }
};

题解: 递归


2.对称二叉树( LeetCode 101 )

难度: 简单
题目表述:
给你一个二叉树的根节点 root , 检查它是否轴对称。
代码(C++):

class Solution {
public:
	// 迭代
	bool check(TreeNode* p, TreeNode* q) {
        queue<TreeNode*> que; //stack 也可
        que.push(p);
        que.push(q);
        while (!que.empty()) {
            p = que.front(); que.pop();
            q = que.front(); que.pop();
            if (!p && !q) continue;
            if (!p || !q || p->val != q->val) return false;
            que.push(p->left);
            que.push(q->right);
            que.push(p->right);
            que.push(q->left);
        }
        return true;
    }
	// 递归
    bool check(TreeNode* p, TreeNode* q) {
        if (!p && !q) return true;
        if (!p || !q) return false;
        return p->val == q->val && check(p->left, q->right) && check(p->right, q->left);
    }
    bool isSymmetric(TreeNode* root) {
        return check(root, root);
    }
};

题解:
左子树的左孩子 == 右子树的右孩子
左子树的右孩子 == 右子树的左孩子


3.最大二叉树( LeetCode 654 )

难度: 中等
题目表述:
给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
创建一个根节点,其值为 nums 中的最大值。
递归地在最大值 左边 的 子数组前缀上 构建左子树。
递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 最大二叉树 。
代码(C++):

class Solution {
public:
	// 递归
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        return construct(nums, 0, nums.size() - 1);
    }
    TreeNode* construct(vector<int>& nums, int left, int right) {
        if (left > right) return nullptr;
        int best = left;
        for (int i = left + 1; i <= right; i++) {
            if (nums[i] > nums[best]) {
                best = i;
            }
        }
        TreeNode* node = new TreeNode(nums[best]);
        node->left = construct(nums, left, best - 1);
        node->right = construct(nums, best + 1, right);
        return node;
    }
    // 单调栈
    TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
        vector<TreeNode*> tree(nums.size());
        vector<int> stk;
        for (int i = 0; i < nums.size(); i++) {
            tree[i] = new TreeNode(nums[i]);
            while (!stk.empty() && nums[stk.back()] < nums[i]) {
                tree[i]->left = tree[stk.back()];
                stk.pop_back();
            }
            if (!stk.empty()) {
                tree[stk.back()]->right = tree[i];
            }
            stk.push_back(i);
        }
        return tree[stk.front()];
    }
};

题解: 递归 / 单调栈


4.平衡二叉树( LeetCode 110 )

难度: 简单
题目表述:
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
代码(C++):

class Solution {
public:
	// 自顶向下的递归 前序 求深度
    bool isBalanced(TreeNode* root) {
        if (!root) return true;
        return abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
    }
    int height(TreeNode* root) {
        if (!root) return 0;
        return max(height(root->left), height(root->right)) + 1;
    }
    // 自底向上的递归 后序 求高度
    bool isBalanced(TreeNode* root) {
        return height(root) >= 0;
    }
    int height(TreeNode* root) {
        if (!root) return 0;
        int leftHeight = height(root->left);
        int rightHeight = height(root->right);
        if (leftHeight == -1 || rightHeight == -1 || abs(leftHeight - rightHeight) > 1)
            return -1;
        else
            return max(leftHeight, rightHeight) + 1;
    }
};

题解:
自顶向下的递归(前序 求深度),类似于二叉树的前序遍历,从根开始。对于同一个节点,函数
height 会被height和isBalanced重复调用,时间复杂度高O(n^2)。
自底向上的递归(后序 求高度),类似于二叉树的后序遍历,到根结束。每个节点的计算高度和判断是否平衡都只需要处理一次,时间复杂度O(n)。
max(left, right)+1记录高度


5.二叉树的最小深度( LeetCode 111 )

难度: 简单
题目表述:
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
代码(C++):

class Solution {
public:
	// 深度优先搜索
    int minDepth(TreeNode* root) {
        if (!root) {
            return 0;
        }
        int left = minDepth(root->left);
        int right = minDepth(root->right);
        return (!left || !right) ? (left + right) + 1 : min(left, right) + 1;
    }
    // 广度优先搜索
    int minDepth(TreeNode* root) {
        if (!root) {
            return 0;
        }
        queue<pair<TreeNode*, int>> que;
        que.emplace(root, 1);
        while (!que.empty()) {
            TreeNode* node = que.front().first;
            int depth = que.front().second;
            que.pop();
            if (!node->left && !node->right) return depth;
            if (node->left) que.emplace(node->left, depth + 1);
            if (node->right) que.emplace(node->right, depth + 1);
        }
        return 0;
    }
};

题解:
深度优先搜索:时间复杂度O(N),空间复杂度O(H) ,平均情况下树的高度与节点数的对数正相关 即O(logN)。用栈递归实现。
广度优先搜索:时间复杂度O(N),空间复杂度O(N)。一层一层遍历的性质保证了最先搜索到的叶子节点的深度一定最小。用队列循环实现。


6.二叉树的最大深度( LeetCode 104 )

难度: 简单
题目表述:
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
代码(C++):

class Solution {
public:
	// 深度优先搜索
    int maxDepth(TreeNode* root) {
        if (root == nullptr) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
   	// 广度优先搜索
   	int maxDepth(TreeNode* root) {
        if (!root) return 0;
        queue<TreeNode*> que;
        que.push(root);
        int depth = 0;
        while (!que.empty()) {
            int size = que.size();
            while (size > 0) {
                TreeNode* node = que.front();
                que.pop();
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
                size--;
            }
            depth++;
        }
        return depth;
    }
};

题解:
深度优先搜索:时间复杂度O(N),空间复杂度O(H) ,平均情况下树的高度与节点数的对数正相关 即O(logN)。用栈递归实现。
广度优先搜索:时间复杂度O(N),空间复杂度O(N)。一次拿出一层所有节点。用队列循环实现。


7.完全二叉树的节点个数( LeetCode 222 )

难度: 中等
题目表述:
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
代码(C++):

class Solution {
public:
	// 递归 后序遍历
    int countNodes(TreeNode* root) {
        if (!root) return 0;
        return 1 + countNodes(root->left) + countNodes(root->right);
    }
    // 迭代 层序遍历
    int countNodes(TreeNode* root) {
        queue<TreeNode*> q;
        if (root)  q.push(root);
        int count = 0;
        while (!q.empty()) {
            TreeNode* node = q.front();
            q.pop();
            count++;
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        return count;
    }
    // 完全二叉树特性 满二叉树:2^depth-1
    int countNodes(TreeNode* root) {
        if (!root) return 0;
        TreeNode* left = root->left;
        TreeNode* right = root->right;
        int lDepth = 0, rDepth = 0;
        while (left) {
            left = left->left;
            lDepth++;
        }
        while (right) {
            right = right->right;
            rDepth++;
        }
        if (lDepth == rDepth) return (2 << lDepth) - 1;
        return countNodes(root->left) + countNodes(root->right) + 1;
    }
};

题解:
完全二叉树只有两种情况,1:就是满二叉树,2:最后一层叶子节点没有满。
1.可以直接用 2^depth - 1 来计算,注意这里根节点深度为1。
2.分别递归左孩子和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
时间复杂度:logn*logn | 层数(while循环找深度) * 每次缩减一半(只遍历了不满的那一半边)
空间复杂度:logn | 递归深度


8.二叉树的所有路径( LeetCode 257 )

难度: 简单
题目表述:
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
代码(C++):

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

题解:


9.左叶子之和( LeetCode 404 )

难度: 简单
题目表述:
给定二叉树的根节点 root ,返回所有左叶子之和。
代码(C++):

class Solution {
public:
	// 递归
    int sumOfLeftLeaves(TreeNode* root) {
        if (!root) return 0;
        int sum = 0;
        if (root->left && !root->left->left && !root->left->right) 
            sum = root->left->val;
        return sum + sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
    }
    // 迭代
    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->right) st.push(node->right);
            if (node->left) st.push(node->left);
        }
        return result;
    }
};

题解:
平时我们解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。


10.找树左下角的值( LeetCode 513 )

难度: 中等
题目表述:
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
代码(C++):

class Solution {
public:
int ans = 0;
int maxDepth = 0;
	// 递归 
    void dfs(TreeNode* root, int depth) {
        if (!root) return;
        if (!root->left && !root->right) {
            if (depth > maxDepth) {
                maxDepth = depth;
                ans = root->val;
            }
            return;
        }
        dfs(root->left, depth + 1);
        dfs(root->right, depth + 1);
    }
    int findBottomLeftValue(TreeNode* root) {
        dfs(root, 1);
        return ans;
    }
    // 迭代 层序
    int findBottomLeftValue(TreeNode* root) {
        int ans;
        queue<TreeNode*> que;
        if (root) que.push(root); 
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                if (i == 0) ans = node->val;
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return ans;
    }
};

题解:
遍历顺序无所谓,因为本题没有 中间节点的处理逻辑,只要左优先就行,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。


11.路径总和( LeetCode 112 )

难度: 简单
题目表述:
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
代码(C++):

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (!root) return false;
        if (!root->left && !root->right) {
            return root->val == targetSum;
        }
        return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
    }
};

题解:


12.从前序与中序遍历序列构造二叉树( LeetCode 105 )

难度: 中等
题目表述:
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
代码(C++):

class Solution {
public:
	// 递归
    unordered_map<int, int> index;
    TreeNode* build(vector<int>& preorder, int pre_l, int pre_r, vector<int>& inorder, int in_l, int in_r) {
        if (pre_l > pre_r) return nullptr;
        int root_val = preorder[pre_l];
        int in_root = index[root_val];
        TreeNode* root = new TreeNode(root_val);
        int left_subtree_size = in_root - in_l;
        root->left = build(preorder, pre_l + 1, pre_l + left_subtree_size, inorder, in_l, in_root - 1);
        root->right = build(preorder, pre_l + left_subtree_size + 1, pre_r, inorder, in_root + 1, in_r);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        for (int i = 0; i < n; i++) {
            index[inorder[i]] = i;
        }
        return build(preorder, 0, n - 1, inorder, 0, n - 1);
    }
    // 迭代
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (!preorder.size()) return nullptr;
        stack<TreeNode*> stk;
        TreeNode* root = new TreeNode(preorder[0]);
        stk.push(root);
        int j = 0;
        for (int i = 1; i < preorder.size(); i++) {
            TreeNode* node = stk.top();
            if (node->val != inorder[j]) {
                node->left = new TreeNode(preorder[i]);
                stk.push(node->left);
            } else {
                while (!stk.empty() && stk.top()->val == inorder[j]) {
                    node = stk.top();
                    stk.pop();
                    j++;
                }
                node->right = new TreeNode(preorder[i]);
                stk.push(node->right);
            }
        }
        return root;
    }
};

题解: 递归 / 迭代
迭代(用栈实现):在前序遍历的顺序中,只有左节点的情况下,这些节点的顺序和它们在中序遍历中出现的顺序一定是相反的。


13.从中序与后序遍历序列构造二叉树( LeetCode 106 )

难度: 中等
题目表述:
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
代码(C++):

class Solution {
public:
unordered_map<int, int> map;
    TreeNode* tree(vector<int>& inorder, vector<int>& postorder, int in_l, int in_r, int po_l, int po_r) {
        if (in_l > in_r || po_l > po_r) return nullptr;
        int root_val = postorder[po_r];
        int root_idx = map[root_val];
        int left_len = root_idx - in_l;
        TreeNode* root = new TreeNode(root_val);
        root->left = tree(inorder, postorder, in_l, root_idx - 1, po_l, po_l + left_len - 1);
        root->right = tree(inorder, postorder, root_idx + 1, in_r, po_l + left_len, po_r - 1);
        return root;
    }
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        int n = inorder.size();
        for (int i = 0; i < n; i++) {
            map[inorder[i]] = i;
        }
        return tree(inorder, postorder, 0, n - 1, 0, n - 1);
    }
};

题解:


14.合并二叉树( LeetCode 617 )

难度: 简单
题目表述:
给你两棵二叉树: root1 和 root2 。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
代码(C++):

class Solution {
public:
	// 递归 前序
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if (!root1) return root2;
        if (!root2) return root1;
        TreeNode* root = new TreeNode(root1->val + root2->val);
        root->left = mergeTrees(root1->left, root2->left);
        root->right = mergeTrees(root1->right, root2->right);
        return root;
    }
    // 迭代 层序
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if (!root1) return root2;
        if (!root2) return root1;
        queue<TreeNode*> q;
        q.push(root1);
        q.push(root2);
        while (!q.empty()) {
            TreeNode* node1 = q.front(); q.pop();
            TreeNode* node2 = q.front(); q.pop();
            node1->val += node2->val;
            if (node1->left && node2->left) {
                q.push(node1->left);
                q.push(node2->left);
            }
            if (node1->right && node2->right) {
                q.push(node1->right);
                q.push(node2->right);
            }
            if (!node1->left && node2->left) {
                node1->left = node2->left;
            }
            if (!node1->right && node2->right) {
                node1->right = node2->right;
            }
        }
        return root1;
    }
};

题解:


15.二叉搜索树中的搜索( LeetCode 700 )

难度: 简单
题目表述:
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
代码(C++):

class Solution {
public:
    TreeNode* searchBST(TreeNode* root, int val) {
        if (!root) return nullptr;
        if (root->val == val) {
            return root;
        }
        return searchBST(val < root->val ? root->left : root->right, val);
    }
    TreeNode *searchBST(TreeNode *root, int val) {
        while (root) {
            if (val == root->val) {
                return root;
            }
            root = val < root->val ? root->left : root->right;
        }
        return nullptr;
    }
};

题解: 二叉搜索树是一个有序树
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉搜索树
一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,再走右分支。而对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。


16.验证二叉搜索树( LeetCode 98 )

难度: 中等
题目表述:
有效 二叉搜索树定义如下:节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。
代码(C++):

class Solution {
public:
    // 1.验证二叉搜索树,相当于判断 中序 遍历是否是递增的
	long long maxVal = LONG_MIN;
    bool isValidBST(TreeNode* root) {
        if (!root) return true;
        bool left = isValidBST(root->left);
        if (root->val <= maxVal) return false;
        maxVal = root->val;
        bool right = isValidBST(root->right);
        return left && right;
    }
    // 如果测试数据中有 longlong的最小值,可以取前一节点的数值来比较 
    TreeNode* pre = nullptr;
    bool isValidBST(TreeNode* root) {
        if (!root) return true;
        bool left = isValidBST(root->left);
        if (pre && root->val <= pre->val) return false;
        pre = root;
        bool right = isValidBST(root->right);
        return left && right;
    }
    // 2.迭代 中序
    bool isValidBST(TreeNode* root) {
        TreeNode* pre = nullptr;
        stack<TreeNode*> stk;
        while (root || !stk.empty()) {
            if (root) {
                stk.push(root);
                root = root->left;
            } else {
                root = stk.top(); stk.pop();
                if (pre && root->val <= pre->val) return false;
                pre = root;
                root = root->right;
            }
        }
        return true;
    }
};

题解:
陷阱一:不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了,而是左子树都小于中间节点,右子树都大于中间节点。
陷阱二:在一个有序序列求最值的时候,最值可能就是int 或者 longlong的最小值。因此可以取前一节点的数值来比较 。


17.二叉搜索树的最小绝对差( LeetCode 530 )

难度: 简单
题目表述:
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。差值是一个正数,其数值等于两值之差的绝对值。
代码(C++):

class Solution {
public:
int minDiff = INT_MAX;
int pre = -1; // or TreeNode* pre = nullptr;
    void dfs(TreeNode* root) {
        if (!root) return;
        dfs(root->left);
        if (pre != -1 && root->val - pre < minDiff) {
            minDiff = root->val - pre;
        }
        pre = root->val;
        dfs(root->right);
    }
    int getMinimumDifference(TreeNode* root) {
        dfs(root);
        return minDiff;
    }
};

题解: 中序遍历二叉搜索树等于遍历有序数组
在有序数组求任意两数最小值差等价于相邻两数的最小值差。
需要用pre节点记录cur节点的前一个节点!!!


18.二叉搜索树中的众数( LeetCode 501 )

难度: 简单
题目表述:
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
代码(C++):

class Solution {
public:
vector<int> res;
int max_count = 0;
int count = 1;
TreeNode* pre = nullptr;
    void dfs(TreeNode* root) {
        if (!root) return;
        dfs(root->left);
        if (pre != nullptr) {
            if (pre->val == root->val) {
                count++;
            } else {
                count = 1;
            }
        }
        pre = root;
        if (count > max_count) {
            max_count = count;
            res.clear();
            res.push_back(root->val);
        } 
        if (count == max_count) {
            res.push_back(root->val);
        }
        dfs(root->right);
    }
    vector<int> findMode(TreeNode* root) {
        dfs(root);
        return res;
    }
};

题解: 中序遍历二叉搜索树等于遍历有序数组
适时清空结果集
需要用pre节点记录cur节点的前一个节点!!!


19.二叉树的最近公共祖先( LeetCode 236 )

难度: 中等
题目表述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
代码(C++):

class Solution {
public:
	// 1.递归 返回值TreeNode* 
	TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == p || root == q || !root) return root;
        TreeNode* left = lowestCommonAncestor(root->left, p, q);
        TreeNode* right = lowestCommonAncestor(root->right, p, q);
        if (left && right) return root;
        if (left) return left;
        return right;
    }
	// 2.递归 返回值bool
    TreeNode* ans;
    bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (!root) return false;
        bool left = dfs(root->left, p, q);
        bool right = dfs(root->right, p, q);
        if (left && right || (root == p || root == q) && (left || right)){
            ans = root;
        }
        return left || right || root == p || root == q;
    } 
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, p, q);
        return ans;
    }
    // 3.哈希表
    unordered_map<int, TreeNode*> fa;
    unordered_map<int, bool> vis;
    void dfs(TreeNode* root) {
        if (root->left) {
            fa[root->left->val] = root;
            dfs(root->left);
        }
        if (root->right) {
            fa[root->right->val] = root;
            dfs(root->right);
        }
    } 
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (!root) return NULL;
        fa[root->val] = NULL;
        dfs(root);
        while (p) {
            vis[p->val] = true;
            p = fa[p->val];
        }
        while (q) {
            if (vis[q->val]) return q;
            q = fa[q->val];
        }
        return NULL;
    }
};

题解: 递归 / 哈希表
后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。
递归:左右子树分别去判断是否包含p or q,左右子树都包含就找到了
哈希表存储父节点


20.二叉搜索树的最近公共祖先( LeetCode 235 )

难度: 中等
题目表述:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
代码(C++):

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

题解:
不用使用回溯,二叉搜索树自带方向性,可以方便的从上向下查找目标区间,遇到目标区间内的节点,直接返回。


21.二叉搜索树中的插入操作( LeetCode 701 )

难度: 中等
题目表述:
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
代码(C++):

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

题解:
搜索树是有方向的,可以根据插入元素的数值,决定递归方向。
二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整。


22.删除二叉搜索树中的节点( LeetCode 450 )

难度: 中等
题目表述:
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
代码(C++):

class Solution {
public:
	// 递归
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (!root) return nullptr;
        if (root->val == key) {
            if (!root->right) return root->left;
            TreeNode* node = root->right;
            while (node->left) {
                node = node->left;
            }
            node->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;
    }
    // 迭代
    TreeNode* deleteOneNode(TreeNode* cur) {
        if (!cur) return nullptr;
        if (!cur->right) return cur->left;
        TreeNode* node = cur->right;
        while (node->left) {
            node = node->left;
        }
        node->left = cur->left;
        return cur->right;
    }
    TreeNode* deleteNode(TreeNode* root, int key) {
        if (!root) return root;
        TreeNode* cur = root;
        TreeNode* pre = nullptr;
        while (cur) {
            if (cur->val == key) break;
            pre = cur;
            if (cur->val > key) cur = cur->left;
            else cur = cur->right;
        }
        if (pre == nullptr) return deleteOneNode(cur);
        if (pre->left && pre->left->val == key) pre->left = deleteOneNode(cur);
        if (pre->right && pre->right->val == key) pre->right = deleteOneNode(cur);
        return root;
    }
};

题解:
二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整。


23.修剪二叉搜索树( LeetCode 669 )

难度: 中等
题目表述:
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
代码(C++):

class Solution {
public:
	// 递归
    TreeNode* trimBST(TreeNode* root, int low, int high) {
        if (!root) return nullptr;
        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* trimBST(TreeNode* root, int low, int high) {
        while (root && (root->val < low || root->val > high)) {
            if (root->val < low) root = root->right;
            else root = root->left;
        }
        TreeNode* cur = root;
        while (cur) {
            while (cur->left && cur->left->val < low) {
                cur->left = cur->left->right;
            }
            cur = cur->left;
        }
        cur = root;
        while (cur) {
            while (cur->right && cur->right->val > high) {
                cur->right = cur->right->left;
            }
            cur = cur->right;
        }
        return root;
    }
};

题解:
因为二叉搜索树的有序性,不需要使用栈模拟递归的过程。


24.将有序数组转换为二叉搜索树( LeetCode 108 )

难度: 简单
题目表述:
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
代码(C++):

class Solution {
public:
    TreeNode* dfs(vector<int>& nums, int l, int r) {
        if (l > r) return nullptr;
        int mid = l + ((r - l) / 2);
        TreeNode* root = new TreeNode(nums[mid]);
        root->left = dfs(nums, l, mid - 1);
        root->right = dfs(nums, mid + 1, r);
        return root;
    }
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return dfs(nums, 0, nums.size() - 1);
    }
    // 迭代
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        queue<TreeNode*> nq;
        queue<int> lq, rq;
        TreeNode* root = new TreeNode(0);
        nq.push(root);
        lq.push(0);
        rq.push(nums.size() - 1);
        while (!nq.empty()) {
            TreeNode* node = nq.front(); nq.pop();
            int l = lq.front(); lq.pop();
            int r = rq.front(); rq.pop();
            int mid = (l + r) / 2;
            node->val = nums[mid];
            if (l <= mid - 1) {
                node->left = new TreeNode(0);
                nq.push(node->left);
                lq.push(l);
                rq.push(mid - 1);
            }
            if (r >= mid + 1) {
                node->right = new TreeNode(0);
                nq.push(node->right);
                lq.push(mid + 1);
                rq.push(r);
            }
        }
        return root;
    }
};

题解:
通过递归函数的返回值来增删二叉树


25.把二叉搜索树转换为累加树( LeetCode 538 )

难度: 中等
题目表述:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
代码(C++):

class Solution {
public:
int sum = 0;
	// 递归
    TreeNode* convertBST(TreeNode* root) {
        if (!root) return nullptr;
        convertBST(root->right);
        root->val += sum;
        sum = root->val;
        convertBST(root->left);
        return root;
    }
    // 迭代
    TreeNode* convertBST(TreeNode* root) {
        stack<TreeNode*> st;
        TreeNode* cur = root;
        while (cur != NULL || !st.empty()) {
            if (cur != NULL) {
                st.push(cur);
                cur = cur->right;   
            } else {
                cur = st.top();     
                st.pop();
                cur->val += sum;
                sum = cur->val;
                cur = cur->left;   
            }
        }
        return root;
    }
};

题解:
二叉搜索树可看作递增数组,采用反中序遍历(右->中->左)即可从后向前累加sum。


小结

二叉树节点的深度: 指从根节点到该节点的最长简单路径边的条数。自顶向下 前序
二叉树节点的高度: 指从该节点到叶子节点的最长简单路径边的条数。自底向上 后序

最大二叉树用:递归 / 两侧单调递减栈
平衡二叉树的高度用:max(left, right)+1
寻找最小公共祖先:从底向上遍历,只能通过后序遍历,后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。 / 哈希存储父节点。
二叉搜索树:中序遍历二叉搜索树等于遍历有序数组,建议使用两个前后指针作比较,即用pre节点记录cur节点的前一个节点!!!

TreeNode* pre = nullptr;
void dfs(TreeNode* root) {
    if (!root) return;
    dfs(root->left);
    // 处理节点root  
    ...
    pre = root; // 当前节点赋值给pre
    dfs(root->right);
}

二叉搜索树添加节点只需要在叶子上添加就可以的,不涉及到结构的调整,而删除节点操作涉及到结构的调整。

深度优先搜索 DFS:用 递归 / 栈迭代 实现。
广度优先搜索 BFS:用 队列 循环实现。


递归函数是否需要返回值:

  1. 搜索整棵二叉树且不用处理递归返回值,不需要返回值。
  2. 搜索整棵二叉树且需要处理递归返回值,需要返回值。
left = 递归函数(root->left);  // 左
right = 递归函数(root->right); // 右
left与right的逻辑处理;         // 中 
  1. 搜索其中一条符合条件的路径(或者一个节点),需要返回值,因为遇到符合条件的路径(或节点)就要及时返回!
if (递归函数(root->left)) return ;
if (递归函数(root->right)) return ;

注:在递归函数有返回值的情况下:如果要搜索一条边,递归函数返回值不为空的时候,立刻返回,如果搜索整个树,直接用一个变量left、right接住返回值,这个left、right后序还有逻辑处理的需要,也就是后序遍历中处理中间节点的逻辑(也是回溯)。


选择什么遍历顺序:

  • 二叉树的构造: 一定前序,先构造中节点。
  • 普通二叉树的属性(最大、最小深度、对称、平衡):一般是后序,一般要通过递归函数的返回值再做中间节点的计算。
  • 二叉搜索树的属性: 一定是中序,因中序有序性。

参考链接

玩转 LeetCode 高频 100 题
https://blog.csdn.net/qq_45829112/article/details/123565427
LeetCode 刷题攻略

你可能感兴趣的:(LeetCode刷题专栏,leetcode,算法,数据结构)