leetcode-树

Tree

前言

如无特殊说明,二叉树节点的定义为:

//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) {}
};

二叉树模板

#include <iostream>
#include <vector>
#include <string>

using namespace std;

// 树的节点定义为结构体,而不是类!!
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode():val(0), left(nullptr), right(nullptr) {}
    TreeNode(int v):val(v), left(nullptr), right(nullptr) {}
    TreeNode(int v, TreeNode* l, TreeNode* r):val(v), left(l), right(r) {}
};

// 前序遍历创建二叉树
TreeNode* createBinaryTree(const vector<string>& vals, int pos)
{
    if (pos >= vals.size())
        return nullptr;
    
    if (vals[pos] == "#")
        return nullptr;
    
    TreeNode* root = new TreeNode(stoi(vals[pos]));
    root->left = createBinaryTree(vals, 2 * pos + 1);
    root->right = createBinaryTree(vals, 2 * pos + 2);

    return root;
}

// 后序遍历销毁二叉树
void destroyBinaryTree(TreeNode* root)
{
    if (root == nullptr)
        return;
    
    destroyBinaryTree(root->left);
    destroyBinaryTree(root->right);

    delete root;
    root = nullptr;
}

int main(int argc, char* argv[])
{
    vector<string> vals{"1", "2", "3", "#", "4", "5", "#"};
    TreeNode* root = createBinaryTree(vals, 0);

    // ... do something here

    destroyBinaryTree(root);
    root = nullptr;

    return 0;
}

144. 二叉树的前序遍历

问题描述:
给你二叉树的根节点 root ,返回它节点值的前序遍历。
示例:
在这里插入图片描述

输入:root = [1,null,2,3]
输出:[1,2,3]

解析:

  1. 递归:先实现业务逻辑,再递归调用搜索左子树,最后递归调用搜索右子树。
  2. 迭代:使用栈辅助遍历。

代码实现:
递归:

/*时间复杂度为O(N),空间复杂度为O(N)*/
class Solution {
private:
    void preorder(TreeNode* root, vector<int>& valVec)
    {
        if (root == nullptr)
            return;
        
        valVec.push_back(root->val);
        preorder(root->left, valVec);
        preorder(root->right, valVec);
    }
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ret;
        preorder(root, ret);
        return ret;
    }
};

迭代:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> ret;
        stack<TreeNode*> stk;
        TreeNode* node = root;

        while (!stk.empty() || node != nullptr)
        {
            if (node != nullptr)
            {
                ret.push_back(node->val);
                stk.push(node);
                node = node->left;
            }
            else
            {
                node = stk.top();
                stk.pop();
                node = node->right;
            }
        }
        return ret;
    }
};

94. 二叉树的中序遍历

问题描述:
给定一个二叉树的根节点 root ,返回它的中序遍历。
解析:

  1. 递归:先递归调用搜索左子树,再实现业务逻辑,最后递归调用搜索右子树。
  2. 迭代:使用栈辅助遍历。

代码实现:
递归:

class Solution {
private:
    void inOrder(TreeNode* node, vector<int> &iVec)
    {
        if (node == nullptr)
            return;
        
        inOrder(node->left, iVec);
        iVec.push_back(node->val);
        inOrder(node->right, iVec);
    }
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> iVec;
        inOrder(root, iVec);
        return iVec;
    }
};

迭代:

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> stk;
        vector<int> ret;
        TreeNode* node = root;

        while (!stk.empty() || node)
        {
            if (node)
            {
                stk.push(node);
                node = node->left;
            }
            else
            {
                node = stk.top();
                stk.pop();
                ret.push_back(node->val);
                node = node->right;
            }
        }
        return ret;
    }
};

145. 二叉树的后序遍历

问题描述:
给定一个二叉树,返回它的后序遍历。
示例:

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [3,2,1]

解析:

  1. 递归,将实现放在左子树和右子树递归函数调用之后。
  2. 迭代。
    我们可以根据前序遍历和后序遍历的联系得到后序遍历。
    前序遍历:root, left, right
    后序遍历:left, right, root
    首先,调换left与right的位置,得到 root, right, left,
    然后再翻转reverse,就可以得到left, right, root后序遍历顺序。
    所以我们可以根据前序遍历的代码来改为后序遍历。
    唯一的区别就是,前序遍历的时候是先右后左,后续遍历是先左后右。
    最后再将结果进行翻转即可。

代码实现:

class Solution {
private:
    void postorder(TreeNode* root, vector<int>& valVec)
    {
        if (root == nullptr)
            return;
        
        postorder(root->left, valVec);
        postorder(root->right, valVec);
        valVec.push_back(root->val);
    }
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> ret;
        postorder(root, ret);
        return ret;
    }
};

迭代:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> stk;
        vector<int> ret;
        TreeNode* node = root;
        TreeNode* prev; // 关键!记录上一次遍历的节点

        while (!stk.empty() || node)
        {
            if (node)
            {
                stk.push(node);
                node = node->left;
            }
            else
            {
                node = stk.top();
                // 需要判断右节点是不是之前遍历过的
                if (node->right && node->right != prev)
                {
                    node = node->right;
                }
                else
                {
                    ret.push_back(node->val);
                    stk.pop();
                    prev = node;
                    node = nullptr;
                }
            }
        }
        return ret;
    }
};

783. 二叉搜索树节点最小距离

问题描述:
给你一个二叉搜索树的根节点 root ,返回树中任意两不同节点值之间的最小差值 。
如:
二叉搜索树

输入:root = [4,2,6,1,3]
输出:1

解析:

  1. 如果对节点的值进行排序,那某个值与其相邻两个值的差值中的最小值就是该值与其它值差值的最小值,那实际上只要对排序后的值进行遍历,就可以找出最小值。所以排序可以大大减少计算差值和比较差值的次数。
  2. 给定的树是二叉搜索树,即对于每个节点,父节点的值大于左子节点的值,且小于右子节点的值。所以如果对其进行中序遍历,那遍历的值是升序的。
  3. 所以解题思路就是先对二叉树进行中序遍历,将节点的值保存到数组中,此时,数组中的值是升序排序的,然后对该数组中的值进行两两差值计算,找出最小即可。

代码实现:

class Solution {
public:
	// dfs函数中的prev一定要是引用,这样递归时才能让记录上次遍历的节点有效。
    void dfs(TreeNode* root, TreeNode* &prev, int& minDiff)
    {
        if (root == nullptr)
            return;
        
        dfs(root->left, prev, minDiff);
        if (prev)
            minDiff = min(minDiff, root->val - prev->val);
        prev = root;
        dfs(root->right, prev, minDiff);
    }
    int getMinimumDifference(TreeNode* root) {
        int minDiff = INT_MAX;
        TreeNode* prev = nullptr;
        dfs(root, prev, minDiff);
        return minDiff;
    }
};

208. 实现 Trie (前缀树)

问题描述:
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。
请你实现 Trie 类:

Trie()
-- 初始化前缀树对象。
void insert(String word)
-- 向前缀树中插入字符串 word 。
boolean search(String word)
-- 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix)
-- 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

解析:

  1. Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
  2. 它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
  3. 它有3个基本性质:
    1> 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
    2> 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
    3> 每个节点的所有子节点包含的字符都不相同。
    好比假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:
    在这里插入图片描述

代码实现:

class Trie {
private:
    const static int MAX_BRANCH_COUNT = 26;
    bool isEnd;
    Trie* next[MAX_BRANCH_COUNT];
public:
    Trie() {
        isEnd = false;
        memset(next, 0, sizeof(next));
    }
    void insert(string word) {
        Trie* node = this;
        for (char& c: word)
        {
        	// 注意这里是node->next[c-'a'],而不是先赋值:node = node->next[c-'a']
            if (node->next[c-'a'] == nullptr)
                node->next[c-'a'] = new Trie();
            node = node->next[c-'a'];//注意迭代的动作要在后面,跟下面查询操作不同!
        }
        node->isEnd = true;
    }
    bool search(string word) {
        Trie* node = this;
        for (char& c: word)
        {
            node = node->next[c-'a'];
            if (node == nullptr)
                return false;
        }
        return node->isEnd;
    }
    bool startsWith(string prefix) {
        Trie* node = this;
        for (char& c: prefix)
        {
            node = node->next[c-'a'];
            if (node == nullptr)
                return false;
        }
        return true;
    }
};

96. 不同的二叉搜索树

问题描述:
给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

解析:
动态规划。
设n个节点存在二叉搜索树的个数为G(n),令f(i)为以i(1<=i<=n)为根的二叉搜索树的个数,则:

G(n) = f(1) + f(2) + ... + f(n)		(1)

当i为根节点时,其左子树节点个数为(i-1)个,右子树节点个数为(n-i),则:

f(i) = G(i-1) * G(n-i)				(2)

结合以上两式,得:

G(n) = G(0)*G(n-1) + G(1) * G(n-2) + ... + G(n-1) * G(0)

代码实现:

class Solution {
public:
    int numTrees(int n) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        dp[1] = 1;

		// G(n) = f(1) + f(2) + ... + f(n)
		// f(i) = G(i-1) * G(n - i) (1 <= i <= n)
        for (int i = 2; i <= n; i++)
        {
            for (int j = 0; j < i; j++)
            {
                dp[i] += dp[j] * dp[i - j - 1];
            }
        }

        return dp[n];
    }
};

98. 验证二叉搜索树

问题描述:
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
提示:
设二叉树节点数为size,则:1 <= size <= 10000

解析:

  1. 二叉搜索树的中序遍历是升序的。
  2. pre表示前一个遍历节点的值,注意类型为long long,且取值为LONG_MIN,因为node->val的取值有可能是INT_MIN。

代码实现:
递归:

class Solution {
public:
    TreeNode* prev = nullptr;
    bool isValidBST(TreeNode* root) {
        if (root == nullptr)
            return true;
        
        bool left = isValidBST(root->left);
        if (!left || (prev && prev->val >= root->val))// 注意这里有等于!!
            return false;
        prev = root;
        bool right = isValidBST(root->right);

        return left&&right;
    }
};

迭代:

class Solution {
public:
    bool isValidBST(TreeNode* root) {
        if (root == nullptr)
            return false;

        TreeNode* prev = nullptr;
        stack<TreeNode*> stk;

        while (!stk.empty() || root)
        {
            if (root)
            {
                stk.push(root);
                root = root->left;
            }
            else
            {
                root = stk.top();
                stk.pop();

                if (prev && prev->val >= root->val)
                    return false;
                
                prev = root;
                root = root->right;
            }
        }

        return true;
    }
};

99. 恢复二叉搜索树

问题描述:
给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。
进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗?
示例1:
在这里插入图片描述

输入:root = [1,3,null,null,2]
输出:[3,1,null,null,2]
解释:3 不能是 1 左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。

示例2:
在这里插入图片描述

输入:root = [3,1,4,null,null,2]
输出:[2,1,4,null,null,3]
解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 和 3 使二叉搜索树有效。

解析:

  1. 二叉搜索树的问题,优先考虑用中序遍历…
  2. 题设二叉树在中序遍历后不会按升序排序,其中"扰乱"排序的就是被交换的节点。
  3. 题设二叉树中序遍历后有两种情况:
    1> 有两组参数满足关系:a(i) < a(i-1)
    如示例1,中序遍历为[3, 2, 1],其中[3,2]和[2,1]满足关系a(i) < a(i-1)
    2> 只有一组参数满足关系:a(i) < a(i-1)
    如示例2,中序遍历为[1, 3, 2, 4],其中只有[3,2]满足关系a(i) < a(i-1)
    编写代码时,需要考虑这两种情况。
  4. 时间复杂度为O(n),空间复杂度为O(h)。(n为节点数,h为层数)

代码实现:
递归:

class Solution {
public:
    TreeNode* prev = nullptr;
    TreeNode* swapNode[2] = {nullptr, nullptr};

    void dfs(TreeNode* root)
    {
        if (root == nullptr)
            return;
        
        dfs(root->left);
        if (prev && prev->val >= root->val)
        {
            if (swapNode[1])
            {
                swapNode[1] = root;
                return;
            }
            else
            {
                swapNode[0] = prev;
                swapNode[1] = root;
            }
        }
        prev = root;
        dfs(root->right);
    }
    void recoverTree(TreeNode* root) {
        dfs(root);
        if (swapNode[0] && swapNode[1])
            swap(swapNode[0]->val, swapNode[1]->val);
        return;
    }
};

迭代:

class Solution {
public:
    void recoverTree(TreeNode* root) {        
        stack<TreeNode*> stk;
        TreeNode* temp = root;
        TreeNode* pre = nullptr;
        TreeNode* pSwap[2] = {nullptr, nullptr};

        while (!stk.empty() || temp != nullptr)
        {
            if (temp != nullptr)
            {
                stk.push(temp);
                temp = temp->left;
            }
            else
            {
                temp = stk.top();
                stk.pop();
                if (pre != nullptr && temp->val <= pre->val)
                {
                    // 找到被错误交换的两节点
                    if (pSwap[0] == nullptr)
                    {
                        pSwap[0] = pre;
                        pSwap[1] = temp;
                    }
                    else
                    {
                        pSwap[1] = temp;
                        break;
                    }
                }
                pre = temp;
                temp = temp->right;
            }
        }

        // 交换节点的值
        if (pSwap[0] && pSwap[1])
			swap(pSwap[0]->val, pSwap[1]->val);
    }
};

100. 相同的树

问题描述:
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

解析:

  1. 采用深度优先搜索(前序遍历)
  2. 如果二叉树都为空,则两个二叉树相同;如果其中一个为空,这两个二叉树一定不相同。
  3. 如果二叉树都不为空,那么判断他们的根节点是否相同,如果不相同的话,两个二叉树一定不相同;如果相同,则分别判断两个二叉树的左子树和右子树是否相同。

代码实现:
递归:

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if (p == nullptr && q == nullptr)
            return true;
        
        if (p == nullptr || q == nullptr)
            return false;
        
        if (p->val != q->val)// 重要!! 判断不相等!
            return false;

        return isSameTree(p->left, q->left)
            && isSameTree(p->right, q->right);
    }
};

迭代:

class Solution {
public:
    bool isSameTree(TreeNode* p, TreeNode* q) {
        queue<TreeNode*> que;
        que.push(p);
        que.push(q);

        while (!que.empty())
        {
            p = que.front();
            que.pop();
            q = que.front();
            que.pop();

            if (p == nullptr && q == nullptr)
                continue;

            if (p == nullptr || q == nullptr)
                return false;

            if (p->val != q->val)
                return false;

            que.push(p->left);
            que.push(q->left);
            que.push(p->right);
            que.push(q->right);
        }

        return true;
    }
};

101. 对称二叉树

问题描述:
给定一个二叉树,检查它是否是镜像对称的。
示例1:

    1
   / \
  2   2
 / \ / \
3  4 4  3
则该二叉树 [1,2,2,3,4,4,3] 是对称的。

示例2:

    1
   / \
  2   2
   \   \
   3    3
则二叉树[1,2,2,null,3,null,3] 则不是镜像对称的:

解析:

  1. 递归
  2. 迭代,使用队列

代码实现:
递归:

class Solution {
public:
    bool dfs (TreeNode* left, TreeNode* right)
    {
        if (left == nullptr && right == nullptr)
            return true;
        
        if (left == nullptr || right == nullptr)
            return false;
        
        if (left->val != right->val)
            return false;
        
        return dfs(left->left, right->right)
            && dfs(left->right, right->left);
    }
    bool isSymmetric(TreeNode* root) {
        if (root == nullptr)
            return false;
        
        return dfs(root->left, root->right);
    }
};

迭代:

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        queue<TreeNode*> que;
        que.push(root->left);
        que.push(root->right);

		// 注意!!不需要像层序遍历那样设置两层遍历!
        while (!que.empty())
        {
            TreeNode* leftN = que.front();
            que.pop();
            TreeNode* rightN = que.front();
            que.pop();

            if (leftN == nullptr && rightN == nullptr)
                continue; // 注意!!当两者都会空时,不是返回true,而是继续迭代!!

            if (leftN == nullptr || rightN == nullptr)
                return false;

            if (leftN->val != rightN->val)
                return false;

            que.push(leftN->left);
            que.push(rightN->right);
            que.push(leftN->right);
            que.push(rightN->left);
        }

        return true; // 返回true!!
    }
};

剑指 Offer 27. 二叉树的镜像

问题描述:
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
示例:

例如输入:

     4
   /   \
  2     7
 / \   / \
1   3 6   9
镜像输出:

     4
   /   \
  7     2
 / \   / \
9   6 3   1

解析:
递归…
代码实现:
递归:

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

        TreeNode* node = root->left;
        root->left = root->right;
        root->right = node;

        return root;
    }
};

迭代:

class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if (root == nullptr)
            return nullptr;

        queue<TreeNode*> que;
        que.push(root);

        while (!que.empty())
        {
            TreeNode* node = que.front();
            que.pop();

            swap(node->left, node->right);

            if (node->left)
                que.push(node->left);

            if (node->right)
                que.push(node->right);
        }

        return root;
    }
};

102. 二叉树的层序遍历

问题描述:
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回其层序遍历结果:

[
  [3],
  [9,20],
  [15,7]
]

解析:

  1. 使用队列存储每层节点。
  2. 通过队列的size来确定每层节点个数

代码实现:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        if (root == nullptr)
            return ret;
        queue<TreeNode*> que;
        que.push(root);

        while (!que.empty())
        {
            int n = que.size();
            ret.emplace_back(vector<int>{});
            for (int i = 0; i < n; i++)
            {
                TreeNode* node = que.front();
                que.pop();
                ret.back().emplace_back(node->val);

                if (node->left)
                    que.push(node->left);
                
                if (node->right)
                    que.push(node->right);
            }
        }
        return ret;
    }
};

103. 二叉树的锯齿形层序遍历

问题描述:
给定一个二叉树,返回其节点值的锯齿形层序遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

返回锯齿形层序遍历结果如下:

[
  [3],
  [20,9],
  [15,7]
]

解析:

  1. 可以使用双栈(stack)实现
  2. 也可以使用双端队列(deque)+flag来实现,向双端队列插入元素可以使用头插(push_front()),也可以使用尾插(push_back()).

代码实现:
双端队列:

class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        if (root == nullptr)
            return ret;

        deque<TreeNode*> deq;
        deq.push_back(root);
        bool flag = false;

        while (!deq.empty())
        {
            int n = deq.size();
            ret.emplace_back(vector<int>{});
            for (int i = 0; i < n; i++)
            {
                TreeNode* node = nullptr;
                if (flag)
                {
                    node = deq.back();
                    deq.pop_back();
                    if (node->right)
                        deq.push_front(node->right);
                    if (node->left)
                        deq.push_front(node->left);
                }
                else
                {
                    node = deq.front();
                    deq.pop_front();
                    if (node->left)
                        deq.push_back(node->left);
                    if (node->right)
                        deq.push_back(node->right);
                }
                ret.back().emplace_back(node->val);
            }
            flag = !flag;
        }
        return ret;
    }
};

双栈:

class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        if (root == nullptr)
            return ret;
        
        stack<TreeNode*> lStack;
        stack<TreeNode*> rStack;
        lStack.push(root);

        while (!lStack.empty() || !rStack.empty())
        {
            vector<int> temp;
            if (!lStack.empty())
            {
                while (!lStack.empty())
                {
                    TreeNode* tempNode = lStack.top();
                    temp.push_back(tempNode->val);
                    if (tempNode->left)
                        rStack.push(tempNode->left);
                    if (tempNode->right)
                        rStack.push(tempNode->right);
                    lStack.pop();
                }
                ret.push_back(temp);// 此处可用emplace_back,以提高效率
            }
            else
            {
                while (!rStack.empty())
                {
                    TreeNode* tempNode = rStack.top();
                    temp.push_back(tempNode->val);
                    if (tempNode->right)
                        lStack.push(tempNode->right);
                    if (tempNode->left)
                        lStack.push(tempNode->left);
                    rStack.pop();
                }
                ret.push_back(temp);// 此处可用emplace_back,以提高效率
            }
        }
        return ret;
    }
}

104. 二叉树的最大深度

问题描述:
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

则最大深度为3。

解析:

  1. 可以用深度优先搜索解决
  2. 也可以用广度优先搜索解决。

代码实现:
DFS:

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr)
            return 0;

        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }
}

BFS:

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (root == nullptr)
            return 0;
        
        queue<TreeNode*> nodeQue;
        nodeQue.push(root);
        int maxDep = 0;

        while (!nodeQue.empty())
        {
            int queSize = nodeQue.size();
            for (int i = 0; i < queSize; i++)
            {
                TreeNode* tempNode = nodeQue.front();
                if (tempNode->left)
                    nodeQue.push(tempNode->left);
                if (tempNode->right)
                    nodeQue.push(tempNode->right);
                nodeQue.pop();
            }
            maxDep++;
        }
        return maxDep;
    }
}

105. 从前序与中序遍历序列构造二叉树

问题描述:
根据一棵树的前序遍历与中序遍历构造二叉树。
注意:
你可以假设树中没有重复的元素。
示例:

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下二叉树:

    3
   / \
  9  20
    /  \
   15   7

解析:
递归:

  1. 对于前序遍历和中序遍历,存在如下特征:
    在这里插入图片描述
    而左子树或者右子树中又存在如上关系,可以递归查找。
  2. 可以使用map存储中序遍历的节点值及其索引值,提高查找根节点的效率。

迭代:

  1. 定义一个stack来维护当前节点的所有还未考虑过右儿子的祖先节点

代码实现:
递归:

class Solution {
private:
    unordered_map<int, int> indexMap;
    TreeNode* buildTreeRe(vector<int>& preorder, vector<int>& inorder, int preLeft, int preRight, int inLeft, int inRight)
    {
        if (preLeft > preRight)
            return nullptr;

        TreeNode* root = new TreeNode(preorder[preLeft]);
        int rootIndex = indexMap[root->val];
        int leftTreeSize = rootIndex - inLeft;

        root->left  = buildTreeRe(preorder, inorder, preLeft+1, preLeft+leftTreeSize, inLeft, rootIndex-1);
        root->right = buildTreeRe(preorder, inorder, preLeft+leftTreeSize+1, preRight, rootIndex+1, inRight);
        return root;
    }
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int nodeCnt = inorder.size();
        for (int i = 0; i < nodeCnt; i++)
            indexMap[inorder[i]] = i;
        
        return buildTreeRe(preorder, inorder, 0, nodeCnt - 1, 0, nodeCnt - 1);
    }
};

迭代:

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.size() <= 0 || inorder.size() <= 0)
            return nullptr;
        
        TreeNode* root = new TreeNode(preorder[0]);
        stack<TreeNode*> stk;
        stk.push(root);
        int inOrderIndex = 0;

        for (int i = 1; i < preorder.size(); i++)
        {
            int preOrderVal = preorder[i];
            TreeNode* node = stk.top();
            if (node->val != inorder[inOrderIndex]) //构建左节点
            {
                node->left = new TreeNode(preOrderVal);
                stk.push(node->left);
            }
            else // 构建右节点
            {
                while (!stk.empty() && stk.top()->val == inorder[inOrderIndex])
                {
                    node = stk.top();
                    stk.pop();
                    inOrderIndex++;
                }
                node->right = new TreeNode(preOrderVal);
                stk.push(node->right);
            }
        }
        return root;
    }
}

108. 将有序数组转换为二叉搜索树

问题描述:
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
示例:
在这里插入图片描述

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]

解析:

  1. 取数组中中间的那个元素为根节点,并以此递归。
  2. 要特别注意边界的判断,和中点索引值的计算。

代码实现:

class Solution {
private:
    TreeNode* sortedArrayToBSTRe(vector<int>& nums, int begin, int end)
    {
        if (begin > end)// 关键!!边界判断
            return nullptr;
        
        int rootIndex = (begin + end + 1) >> 1;// 关键!!计算中点索引值
        TreeNode* root = new TreeNode(nums[rootIndex]);

        root->left  = sortedArrayToBSTRe(nums, begin, rootIndex - 1);
        root->right = sortedArrayToBSTRe(nums, rootIndex + 1, end);
        
        return root;
    }
public:
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return sortedArrayToBSTRe(nums, 0, nums.size() - 1);
    }
};

110. 平衡二叉树

问题描述:
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

解析:

  1. 采用后序遍历,先遍历到底,然后自底向顶判断是否是平衡二叉树。
  2. 关于getHeight()的返回值,如果二叉树是平衡二叉树,则返回树的高度;反之,返回-1。

代码实现:

class Solution {
public:
    int dfs(TreeNode* root)
    {
        if (root == nullptr)
            return 0;
        
        int left = dfs(root->left);
        int right = dfs(root->right);

        if (left < 0 || right < 0 || abs(left - right) > 1)
            return -1;
        
        return max(left, right) + 1;
    }
    bool isBalanced(TreeNode* root) {
        return dfs(root) >= 0;
    }
};

提高效率:

class Solution {
private:
    int getHeight(TreeNode* root)
    {
        if (root == nullptr)
            return 0;
        
        int left = getHeight(root->left);
        if (left == -1)// 提高效率
            return -1;
        int right = getHeight(root->right);
        if (right == -1)// 提高效率
            return -1;

        return abs(left - right) < 2?max(left, right)+1:-1;
    }
public:
    bool isBalanced(TreeNode* root) {
        return getHeight(root) != -1;
    }
}

111. 二叉树的最小深度

问题描述:
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
示例1:
在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]
输出:2

示例2:(注意这种情况!!)
在这里插入图片描述

输入:root = [2,null,3,null,4,null,5,null,6]
输出:5

解析:

  1. 使用后序遍历自底而上的计算最小深度
  2. 与计算树的最大深度相比,需要注意子树只有一种分支的情况。
    也就是递归结束条件的确定:
    1> 叶子节点的定义是左子节点和右子节点都是空的子节点,此时返回1。
    2> 当某节点左右子节点有一个为空时,返回不为空的子节点深度。
    3> 当某节点左右子节点都不会空时,返回左右子节点深度中较小者。

代码实现:

class Solution {
private:
    int minDepthRe(TreeNode* root)
    {
        if (root == nullptr)
            return 0;

		// 1.左右子节点都为空
        if (root->left == nullptr && root->right == nullptr)
            return 1;

        int leftDep = minDepthRe(root->left);
        int rightDep = minDepthRe(root->right);

		// 2.左右子节点有一个为空,其中一个深度为0,所以直接加不会影响且可以简化代码
        if (root->left == nullptr || root->right == nullptr)
            return leftDep+rightDep+1;

		// 3.左右子节点都不会空。
        return min(leftDep, rightDep) + 1;
    }
public:
    int minDepth(TreeNode* root) {
        return minDepthRe(root);
    }
};

极度舒适版…

class Solution {
public:
    int minDepth(TreeNode* root) {
        if (root == nullptr)
            return 0;
        
        int left  = minDepth(root->left);
        int right = minDepth(root->right);

        if (left == 0 || right == 0)
            return max(left, right) + 1;

        return min(left, right) + 1;
    }
};

112. 路径总和

问题描述:
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。

叶子节点是指没有子节点的节点。

示例:
在这里插入图片描述
解析:

  1. 深度优先搜索(DFS):后序遍历。假定从根节点到当前节点的值之和为 val,我们可以将这个大问题转化为一个小问题:是否存在从当前节点的子节点到叶子的路径,满足其路径和为 sum - val。
  2. 广度优先搜索(BFS):使用两个队列分别存储节点和某个路径上数值总和。
  3. 注意题设是查找到叶子节点,所以在判断路径总和是否等于题设之前要先判断该节点的左右节点是否都会空!

代码实现:
DFS:

/*
 * 时间复杂度O(N),空间复杂度O(H)
 */
class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr)
            return false;
        
        if (root->left == nullptr && root->right == nullptr)//判断叶节点
            return targetSum == root->val;// 巧妙!!
        /* // low ...
		if (root->left == nullptr && root->right == nullptr)
		{
			if (targetSum == root->val)
				return true;
			return false;
		}
        */
        
        bool isLeftHas  = hasPathSum(root->left, targetSum - root->val);
        bool isRightHas = hasPathSum(root->right, targetSum - root->val);

        return isLeftHas || isRightHas;
    }
};

BFS:

class Solution {
public:
    bool hasPathSum(TreeNode* root, int targetSum) {
        if (root == nullptr)
            return false;

        queue<TreeNode*> nodeQue;
        nodeQue.push(root);
        queue<int> valQue;
        valQue.push(root->val);

        while (!nodeQue.empty())
        {
            int nodeCnt = nodeQue.size();
            for (int i = 0; i < nodeCnt; i++)
            {
                TreeNode* tempNode = nodeQue.front();
                int tempVal = valQue.front();
                nodeQue.pop();
                valQue.pop();

                if (tempNode->left == nullptr && tempNode->right == nullptr)//判断叶节点。
                {
                    if (targetSum == tempVal)
                        return true;
                }
                else
                {
                    if (tempNode->left)
                    {
                        nodeQue.push(tempNode->left);
                        valQue.push(tempVal + tempNode->left->val);
                    }
                    if (tempNode->right)
                    {
                        nodeQue.push(tempNode->right);
                        valQue.push(tempVal + tempNode->right->val);
                    }
                }
            }
        }

        return false;
    }
};

113. 路径总和 II

问题描述:
给 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例:
在这里插入图片描述

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

解析:

  1. dfs,前序遍历,注意pop_back的时机。
  2. bfs,两个queue,一个填入node,一个填入路径(vector<int>),同步push和pop。

代码实现:
DFS:

class Solution {
private:
    vector<vector<int>> ret;
    vector<int> path;
    void dfs(TreeNode* root, int targetSum)
    {
        if (root == nullptr)
            return;

        path.emplace_back(root->val);
        targetSum -= root->val;
        if (root->left == nullptr && root->right == nullptr && targetSum == 0)
        {
            ret.emplace_back(path);
            path.pop_back();
            return;
        }

        dfs(root->left, targetSum);
        dfs(root->right, targetSum);
        
        path.pop_back();//都没找到才pop出去。
    }

public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        dfs(root, targetSum);
        return ret;
    }
};

BFS:

class Solution {
public:
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        vector<vector<int>> ret;
        if (root == nullptr)
            return ret;
        
        queue<TreeNode*> nodeQue;
        nodeQue.push(root);
        vector<int> valVec;
        valVec.push_back(root->val);// total
        valVec.push_back(root->val);// node val
        queue<vector<int>> vecQue;
        vecQue.push(valVec);

        while (!nodeQue.empty())
        {
            int nodeCnt = nodeQue.size();
            for (int i = 0; i < nodeCnt; i++)
            {
                TreeNode* tempNode = nodeQue.front();
                nodeQue.pop();
                valVec = vecQue.front();
                vecQue.pop();

                if (tempNode->left == nullptr && tempNode->right == nullptr
                    && valVec[0] == targetSum)
                {
                    ret.emplace_back(vector<int>(valVec.begin()+1, valVec.end()));
                }

                if (tempNode->left)
                {
                    vector<int> temp = vector<int>(valVec);
                    temp.push_back(tempNode->left->val);
                    temp[0] += tempNode->left->val;
                    vecQue.push(temp);
                    nodeQue.push(tempNode->left);
                }

                if (tempNode->right)
                {
                    vector<int> temp = vector<int>(valVec);
                    temp.push_back(tempNode->right->val);
                    temp[0] += tempNode->right->val;
                    vecQue.push(temp);
                    nodeQue.push(tempNode->right);
                }
            }
        }
        return ret;
    }
};

114. 二叉树展开为链表

问题描述:
给你二叉树的根结点 root ,请你将它展开为一个单链表:

1> 展开后的单链表应该同样使用TreeNode ,
   其中right 子指针指向链表中下一个结点,而左子指针始终为 null 。
2> 展开后的单链表应该与二叉树先序遍历顺序相同。

解析:

  1. 递归:采用前序遍历,将节点存于vector中,然后再一一组成链表。
  2. 不使用辅助空间的方法:
    1> 每次将当前节点的左子树拼在当前左节点最右边的节点上
    2> 然后将当前节点的右指针指向左子树,左指针置为空。
    1
   / \
  2   5
 / \   \
3   4   6

//将 1 的左子树插入到右子树的地方
    1
     \
      2         5
     / \         \
    3   4         6        
//将原来的右子树接到左子树的最右边节点
    1
     \
      2          
     / \          
    3   4  
         \
          5
           \
            6
            
 //将 2 的左子树插入到右子树的地方
    1
     \
      2          
       \          
        3       4  
                 \
                  5
                   \
                    6   
        
 //将原来的右子树接到左子树的最右边节点
    1
     \
      2          
       \          
        3      
         \
          4  
           \
            5
             \
              6         
  
  ......

代码实现:
递归:

/*时间复杂度 O(n), 空间复杂度O(n)*/
class Solution {
private:
    void prevOrder(TreeNode* root, vector<TreeNode*>& valVec)
    {
        if (root == nullptr)
            return;
        
        valVec.emplace_back(root);
        prevOrder(root->left, valVec);
        prevOrder(root->right, valVec);
    }
public:
    void flatten(TreeNode* root) {
        vector<TreeNode*> valVec;
        prevOrder(root, valVec);
        // 关键!!valVec.size()的返回值unsigned int,是要转成int
        // 不然,(valVec.size()-1)的结果会自动转换为unsinged int,将是一个很大的值。
        // 而不是预想结果 -1
        for (int i = 0; i < (int)valVec.size() - 1; i++)
        {
            valVec[i]->left = nullptr;
            valVec[i]->right = valVec[i+1];
        }
    }
};

迭代:

/*时间复杂度 O(n), 空间复杂度O(1)*/
class Solution {
public:
    void flatten(TreeNode* root) {
        TreeNode* curr = root;
        TreeNode* rightest = nullptr;
        
        while (curr)
        {
            // 如果左子树存在,那么就需要移动左子树到右子树对应位置上。
            if (curr->left)
            {
                rightest = curr->left;
                
                // 找到左子树最右边的节点
                while (rightest->right)
                    rightest = rightest->right;
                
                // 成链
                // 1. 将当前节点的右子树移动到左子树最右节点上
                // 2. 将当前节点的右子树赋值为左子树
                // 3. 将当前节点的左子树置为空
                rightest->right = curr->right;
                curr->right = curr->left;
                curr->left = nullptr;
            }
            
            // 迭代
            curr = curr->right;
        }
    }
};

剑指 Offer 36. 二叉搜索树与双向链表

问题描述:
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
示例:
在这里插入图片描述
转化为:
在这里插入图片描述
解析:

  1. 中序遍历,使用prev指针记录前一个遍历的节点。
  2. 注意在递归结束之后,需要对头尾指针赋值成环。
    代码实现:
class Solution {
public:
    Node* head = nullptr;
    Node* prev = nullptr;
    void dfs(Node* root)
    {
        if (root == nullptr)
            return;
        
        dfs(root->left);
        if (!head)
            head = root;
        
        if (prev)
        {
            prev->right = root;
            root->left = prev;
        }

        prev = root;

        dfs(root->right);
    }
    Node* treeToDoublyList(Node* root) {
        dfs(root);
        if (head && prev)
        {
            head->left = prev;
            prev->right = head;            
        }
        return head;
    }
};

116. 填充每个节点的下一个右侧节点指针

问题描述:
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
示例:
在这里插入图片描述

输入:root = [1,2,3,4,5,6,7]
输出:[1,#,2,3,#,4,5,6,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。

解析:
使用已建立的next指针来连接下一层的节点。
一棵树中,存在两种类型的next 指针。

  1. 第一种情况是连接同一个父节点的两个子节点。在这里插入图片描述
    它们可以通过同一个节点直接访问到,因此下面的操作即可完成连接。
node.left.next = node.right;
  1. 第二种情况在不同父亲的子节点之间建立连接,这种情况不能直接连接。
    在这里插入图片描述
    如果每个节点有指向父节点的指针,可以通过该指针找到 \text{next}next 节点。如果不存在该指针,则按照下面思路建立连接:
第N层节点之间建立next 指针后,再建立第N+1层节点的next指针。
可以通过next指针访问同一层的所有节点,因此可以使用第N层的next指针,为第 N+1 层节点建立next指针。
也就是:
node.right.next = node.next.left

代码实现:

/*时间复杂度O(n),空间复杂度O(1)*/
class Solution {
public:
    Node* connect(Node* root) {
        if (root == nullptr)
            return nullptr;
        // 始终指向某一层的第一个节点
        Node* firstNode = root;

        while (firstNode->left)
        {
            Node* currNode = firstNode;
            while (currNode)
            {
            	// 1. 需连接的两个节点有共同父节点的情况。
                currNode->left->next = currNode->right;
				
				// 2. 需连接两个节点没有共同父节点的情况。
                if (currNode->next)
                    currNode->right->next = currNode->next->left;

                currNode = currNode->next;
            }
            firstNode = firstNode->left;           
        }

        return root;
    }
};

117. 填充每个节点的下一个右侧节点指针 II

问题描述:
给定一个二叉树,结构如下:

struct Node {
  int val;
  Node *left;
  Node *right;
  Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
示例:
在这里插入图片描述

输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。

解析:

  1. 和题<116. 填充每个节点的下一个右侧节点指针>一样的思路,只不过需要考虑左右子树缺失的问题。这会影响到每行首节点的确定。
  2. 每行首节点是上一行的左右子树遍历中第一个不会空的子节点。

代码实现:

/*时间复杂度O(n),空间复杂度O(1)*/
class Solution {
private:
    void handleFunc(Node* &firstNode, Node* &pre, Node* &nextNode)
    {
        if (firstNode == nullptr)
            firstNode = nextNode;

        if (pre)
            pre->next = nextNode;

        pre = nextNode;
    }
public:
    Node* connect(Node* root) {
        if (root == nullptr)
            return nullptr;

        Node* firstNode = root;
        
        while (firstNode)
        {
            Node* currNode = firstNode;
            Node* pre = nullptr;// 用于保存前一个节点
            firstNode = nullptr;// 保存下一行的起始节点,置为nullptr是因为第一次赋值有效。

            while (currNode)
            {
                if (currNode->left)
                {
                    handleFunc(firstNode, pre, currNode->left);
                }

                if (currNode->right)
                {
                    handleFunc(firstNode, pre, currNode->right);
                }
                currNode = currNode->next;
            }
        }

        return root;
    }
};

129. 求根节点到叶节点数字之和

问题描述:
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:

例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。

计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
示例:
在这里插入图片描述

输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026

解析:

  1. 思路类似于题<113. 路径总和 II>,叶节点的判断是左右子树都为空,当到达叶节点时,总数累加,且当前数字除以10,并返回到上一层节点。

代码实现:

class Solution {
public:
    void dfs(TreeNode* root, int& num, int& sum)
    {
        if (root == nullptr)
            return;
        
        num = num * 10 + root->val;
        if (root->left == nullptr && root->right == nullptr)
        {
            sum += num;
            num /= 10;
            return;
        }

        dfs(root->left, num, sum);
        dfs(root->right, num, sum);

        num /= 10;
    }
    int sumNumbers(TreeNode* root) {
        int sum = 0;
        int num = 0;
        dfs(root, num, sum);
        return sum;
    }
};

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

问题描述:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:
对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,
满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。

解析:
题设二叉树为二叉搜索树,可以利用其性质来优化找公共祖先。
如果当前节点的值比给定的两个都大,那么就到当前节点的左节点查找。
如果当前节点的值比给定的两个都小,那么就到当前节点的右节点查找。
如果当前节点的值在给定节点的值中间或者相等,那么就找到公共祖先节点。
代码实现:
迭代:

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        while (root != nullptr)
        {
            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 nullptr;
    }
};

递归:

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

236. 二叉树的最近公共祖先

问题描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
示例1:
在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

示例2:
在这里插入图片描述

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。

解析:

  1. 采用后序遍历,自底到顶的查询是否有给定节点。可以保证第一次满足条件的节点就是最近的
  2. 若root是q或者p的最近公共祖先,则只可能是以下情况之一:
    ~p和q在root的子树中,且分裂root的异侧
    ~p=root,且q在root的左或右子树中
    ~q=root,且p在root的左或右子树中

代码实现:

class Solution {
public:
    bool dfs (TreeNode* root, TreeNode* p, TreeNode* q, TreeNode* &ant)
    {
        if (root == nullptr)
            return false;
        
        bool left = dfs(root->left, p, q, ant);
        bool right = dfs(root->right, p, q, ant);

        if (left && right
        || (left && (p == root || q == root))
        || (right && (p == root || q == root)))
        {
            ant = root;
        }

        return left || right || p == root || q == root;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* ant = nullptr;
        dfs(root, p, q, ant);
        return ant;
    }
};

543. 二叉树的直径

问题描述:
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例:
给定二叉树

          1
         / \
        2   3
       / \     
      4   5    

返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
解析:

直径长度实际上左右子树的节点数相加。
深度优先遍历,计算左右子树深度的和,并与当前最大直径比较,保存较大者,并返回左右子树中深度最大的那个给上一层计算。
代码实现:

class Solution {
private:
    int longest = 0;
    int dfs(TreeNode* root)
    {
        if (root == nullptr)
            return 0;

        int left = dfs(root->left);
        int right = dfs(root->right);
        longest = max(longest, left+right);

        return max(left, right) + 1;
    }
public:
    int diameterOfBinaryTree(TreeNode* root) {
        dfs(root);
        return longest;
    }
};

124. 二叉树中的最大路径和

问题描述:
路径被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其最大路径和
示例:
在这里插入图片描述

输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

在这里插入图片描述

输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42

解析:

  1. 设置一个全局变量记录最大的路径和。
  2. 递归函数用于计算二叉树一个节点的最大贡献值。
  3. 该函数计算如下
    - 空节点的最大贡献值为0
    - 非空节点的最大贡献值等于节点值与其子节点中较大者之和
    - 子节点小于0的话,可以不取该子节点,即贡献值为0,
    

代码实现:

class Solution {
public:
    int maxSum = INT_MIN; // 初始化为最小值!!
    int dfs (TreeNode* root)
    {
        if (root == nullptr)
            return 0;
        
        int left = max(dfs(root->left), 0); // 注意这里与0比较!
        int right = max(dfs(root->right), 0); // 注意这里与0比较!

        maxSum = max(maxSum, root->val + left + right);

        return root->val + max(left, right);
    }
    int maxPathSum(TreeNode* root) {
        dfs (root);
        return maxSum;
    }
};

617. 合并二叉树

问题描述:
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例:

输入: 
	Tree 1                     Tree 2                  
          1                         2                             
         / \                       / \                            
        3   2                     1   3                        
       /                           \   \                      
      5                             4   7                  
输出: 
合并后的树:
	     3
	    / \
	   4   5
	  / \   \ 
	 5   4   7

解析:
深度遍历
代码实现:
使用原来树上的节点

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
    	// 下面这两个判断非常妙,可以cover其中一个节点为空和两个节点为空的情况
        if (root1 == nullptr)
            return root2;
        
        if (root2 == nullptr)
            return root1;
        
        // 由于需要不破坏原来的树,遇到两树相同位置上的节点都存在的情况,就新建节点!!
        TreeNode * node = new TreeNode(root1->val + root2->val);
        node->left = mergeTrees(root1->left, root2->left);
        node->right = mergeTrees(root1->right, root2->right);

        return node;
    }
};

不使用原来树上的节点:

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
        if (root1 == nullptr && root2 == nullptr)
            return nullptr;
        
        TreeNode * node = new TreeNode();
        if (root1 == nullptr || root2 == nullptr)
        {
            TreeNode* temp = root1==nullptr?root2:root1;
            node->val = temp->val;
            node->left = mergeTrees(temp->left, nullptr);
            node->right = mergeTrees(temp->right, nullptr);
        }
        else
        {
            node->val = root1->val + root2->val;
            node->left = mergeTrees(root1->left, root2->left);
            node->right = mergeTrees(root1->right, root2->right);
        }
        return node;
    }
};

958. 二叉树的完全性检验

问题描述:
给定一个二叉树,确定它是否是一个完全二叉树。
示例:
在这里插入图片描述

输入:[1,2,3,4,5,6]
输出:true
解释:最后一层前的每一层都是满的(即,结点值为 {1} 和 {2,3} 的两层),且最后一层中的所有结点({4,5,6})都尽可能地向左。

解析:
层序遍历,遍历到空节点之后,看该节点后面是否有有效节点,
如果有,那就不是完全二叉树;否则为完全二叉树。
代码实现:

class Solution {
public:
    bool isCompleteTree(TreeNode* root) {
        if (root == nullptr)
            return false;
        
        queue<TreeNode*> que;
        que.push(root);
        bool isCom = false;

        while (!que.empty())
        {
            TreeNode* node = que.front();
            que.pop();

            if (node == nullptr)
            {
                isCom = true;
                continue;
            }

            // 之前遇到nullptr,但是后面还有非空节点,那就说明不是完全!!!
            if (isCom)
                return false;
            
            que.push(node->left);
            que.push(node->right);
        }

        return isCom;
    }
};

剑指 Offer 26. 树的子结构

问题描述:
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
示例:

给定的树 A:

     3
    / \
   4   5
  / \
 1   2
给定的树 B:

   4 
  /
 1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

解析:
双递归…
注意判断子结构的递归跟判断两棵树是否相同的是不同的!!
代码实现:

class Solution {
public:
    bool dfs (TreeNode* A, TreeNode* B)
    {
        if (B == nullptr) // 注意以B树结构为准,如果是空就返回true!!
            return true;
        
        if (A == nullptr) // B不为空,而A为空,那么返回false!!
            return false;
        
        if (A->val != B->val)
            return false;
        
        bool left = dfs(A->left, B->left);
        bool right = dfs(A->right, B->right);

        return left && right;
    }
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if (A == nullptr || B == nullptr)
            return false;

        if (A->val == B->val && dfs(A, B))
            return true;
        
        bool left = isSubStructure(A->left, B);
        bool right = isSubStructure(A->right, B);

        return left || right;
    }
};

199. 二叉树的右视图

问题描述:
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例:
在这里插入图片描述

输入: [1,2,3,null,5,null,4]
输出: [1,3,4]

解析:
层序遍历,先添加右节点,再添加左节点,每次加入一层第一个元素到数组中。
代码实现:

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        vector<int> ret;
        if (root == nullptr)
            return ret;
        queue<TreeNode*> que;
        que.push(root);

        while (!que.empty())
        {
            ret.push_back(que.front()->val); // 只取一层第一个节点的值
            int n = que.size();
            for (int i = 0; i < n; i++)
            {
                TreeNode* node = que.front();
                que.pop();
                
                if (node->right) // 先右
                    que.push(node->right);
                
                if (node->left) // 后左
                    que.push(node->left);
            }
        }

        return ret;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值