leetcode hot 100-二叉树

94.二叉树的中序遍历

class TreeNode:
    def _init_(self,val,left=None,right=None):
        self.val=val
        self.left=left
        self.right=right
class Solution:
    def inorderTraversal(self,root):
        if root is None:
            return []
        left=self.inorderTraversal(root.left)
        right=self.inorderTraversal(root.right)
        return left+[root.val]+right

104.二叉树的最大深度  

二叉树节点的深度:任意一个节点到根节点的距离(取决于深度从0开始还是从1开始)
二叉树节点的高度:指从节点到叶子节点( 叶子节点是指没有子节点的节点)的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始),(叶子节点的高度返回给其父节点,父节点在叶子节点的高度上加一就是父节点的高度,所以先处理子节点,最后处理父节点,就是左右中的顺序,即后序遍历)

例如:上面这棵二叉树,根节点的3的深度是1,节点9和20的深度是2,节点15和7的深度是3;

根节点3的高度是3,节点9和20的高度是2,节点15和7的高度是1. 

前序遍历求深度,后序遍历求高度,而根节点的高度就是二叉树的最大深度,所以可以通过后序求的根节点高度来求的二叉树最大深度。

后序遍历(左右中)来计算树的高度:

确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度。
确定终止条件:如果为空节点的话,就返回0,表示高度为0。
确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度

class TreeNode(object):
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
class Solution(object):
    def maxDepth(self, root):
        return self.getheight(root)
 
    def getheight(self,node):
        if not node:
            return 0
        leftheight=self.getheight(node.left)
        rightheight=self.getheight(node.right)
        height=1+max(leftheight,rightheight)
        return height

226.翻转二叉树

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例 1:

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

示例 2:

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

示例 3:

输入:root = []
输出:[]

想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了 

递归三步曲:
确定递归函数的参数和返回值
确定终止条件:当节点为空时,就返回
确定单层递归的逻辑
 这道题用前序遍历和后序遍历解都可以,但是不能用中序遍历,因为中序遍历是左中右:先把左子树翻转,然后左子树和右子树换位置,接下来处理的右子树其实是第一步已经处理过的左子树,所以用中序遍历的话还有点绕。

前序遍历(递归):

class TreeNode(object):
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
class Solution(object):
    def invertTree(self, root):  # 这里的root其实是指节点,不单单指根节点
        if not root:
            return None
        root.left,root.right=root.right,root.left
        self.invertTree(root.left)
        self.invertTree(root.right)
        return root

后序遍历(递归):

class TreeNode(object):
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
class Solution(object):
    def invertTree(self, root):  # 这里的root指的是节点,不单指根节点
        if not root:
            return None
        self.invertTree(root.left)
        self.invertTree(root.right)
        root.left,root.right=root.right,root.left
        return root

101.对称二叉树

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1:

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

示例 2:

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

判断一个二叉树是否对称,其实就是判断左右子树是否能够相互翻转(左子树的左孩子和右子树的右孩子是否相等,左子树的右孩子和右子树的左孩子是否相等)

凡是需要收集子节点的信息返回给父节点的,都采用后序遍历 

递归三步曲:

1.确定递归函数的参数和返回值

因为要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。

返回值自然是bool类型。

2.确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下称之为左节点右节点)

左节点为空,右节点不为空,不对称,return false
左不为空,右为空,不对称 return false
左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:

左右都不为空,比较节点数值,不相同就return false
此时左右节点不为空,且数值也不相同的情况我们也处理了

3.确定单层递归的逻辑

此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。

比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
如果左右都对称就返回true ,有一侧不对称就返回false 。

class Solution:
    def isSymmetric(self, root):
        if not root:
            return True
        return self.compare(root.left, root.right)
        
    def compare(self, left, right):
        #首先排除空节点的情况
        if left == None and right != None: return False
        elif left != None and right == None: return False
        elif left == None and right == None: return True
        #排除了空节点,再排除数值不相同的情况
        elif left.val != right.val: return False
        
        #此时就是:左右节点都不为空,且数值相同的情况
        #此时才做递归,做下一层的判断
        outside = self.compare(left.left, right.right) #左子树:左、 右子树:右
        inside = self.compare(left.right, right.left) #左子树:右、 右子树:左
        isSame = outside and inside #左子树:中、 右子树:中 (逻辑处理)
        return isSame

 543.二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径 。

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。

两节点之间路径的 长度 由它们之间边数表示。

示例 1:

输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。

思路: 直观的来看是根节点的左子树的深度+右子树的深度,但是注意题目中说路径可能不经过根节点,那么会出现什么什么情况呢?就是例如4,5节点还有子节点时,这时候最长路径就不经过根节点,而是只经过节点2的路径。

解题:首先要写一个递归遍历,求出节点的深度,然后定义一个变量,这个变量存储最大深度(当前节点左右子树深度之和),最后返回这个变量。

递归函数:

1.确定参数和返回值,返回的是树的深度(int 类型),传入函数的是根节点

2.递归终止条件:当前节点(根节点)为空时,返回深度(0)

3.单层递归逻辑:递归遍历左右子树,中间节点的深度为左右子树深度加1

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

class Solution {
public:
    int maxdepth=0; # 定义一个变量接收左右子树深度之和的最大值
    int depth(TreeNode* root){
        if (root==nullptr)return 0;
        int left=depth(root->left);
        int right=depth(root->right);
        maxdepth=max(maxdepth,left+right);  # 更新最大深度
        return max(left,right)+1;
    }
    int diameterOfBinaryTree(TreeNode* root) {
        depth(root);
        return maxdepth;
    }
};

10.二叉树的层序遍历

层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树也就是图论中的广度优先遍历应用在二叉树上。

需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。

使用队列实现二叉树广度优先遍历,动画如下:

102二叉树的层序遍历

class Solution {
public:
    vector<vector<int>> levelOrder(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;  # 定义一个一维数组存放每一层的元素
            // 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                vec.push_back(node->val);
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            result.push_back(vec);
        }
        return result;
    }
};

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

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

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

用数组构造二叉树,一般都是默认从数组中间位置取值作为根节点,这样其实构造成平衡二叉树是自然而然的。 如果数组长度为偶数,取哪一个作为中间节点都可以,只不过是构成了不同的树。


递归三部曲:  

  • 确定递归函数返回值及其参数

本题要构造二叉树,依然用递归函数的返回值来构造中节点的左右孩子。

再来看参数,首先是传入数组,然后就是左下标left和右下标right,在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组。

所以代码如下:

// 左闭右闭区间[left, right]--循环不变量
TreeNode* traversal(vector<int>& nums, int left, int right)
  • 确定递归终止条件

这里定义的是左闭右闭的区间,所以当区间 left > right的时候,就是空节点了

  • 确定单层递归的逻辑

首先取数组中间元素的位置,不难写出int mid = (left + right) / 2;这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法 (opens new window)中尤其需要注意!

所以可以这么写:int mid = left + ((right - left) / 2);

但本题leetcode的测试数据并不会越界,所以怎么写都可以。但需要有这个意识!

取了中间位置,就开始以中间位置的元素构造节点,

TreeNode* root = new TreeNode(nums[mid]);

接着划分区间,root的左孩子接住下一层左区间的构造节点,右孩子接住下一层右区间构造的节点,最后返回root节点。

这里int mid = left + ((right - left) / 2);的写法相当于是如果数组长度为偶数,中间位置有两个元素,取靠左边的。

class Solution {
private:
    TreeNode* traversal(vector<int>& nums, int left, int right) {
        if (left > right) return nullptr;
        int mid = left + ((right - left) / 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;
    }
};

98.验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

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

示例 2:

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。

中序遍历下,输出的二叉搜索树节点的数值是有序序列。

有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。

可以递归中序遍历将二叉搜索树转变成一个数组,然后只要比较一下,这个数组是否是有序的,注意二叉搜索树中不能有重复元素。

递归版本一:
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:
    bool isValidBST(TreeNode* root) {
        vec.clear(); // 不加这句在leetcode上也可以过,但最好加上
        traversal(root);
        for (int i = 1; i < vec.size(); i++) {
            // 注意要小于等于,搜索树里不能有相同元素
            if (vec[i] <= vec[i - 1]) return false;
        }
        return true;
    }
};
递归法(版本二)设定极小值,进行比较:

陷阱:

不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了,要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点

例如:

class Solution {
public:
    long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;

        bool left = isValidBST(root->left);
        // 中序遍历,验证遍历的元素是不是从小到大
        if (maxVal < root->val) maxVal = root->val;
        else return false;
        bool right = isValidBST(root->right);

        return left && right;
    }
};
递归法(版本三)直接取该树的最小值:
class Solution {
public:
    TreeNode* pre = NULL; // 用来记录前一个节点
    bool isValidBST(TreeNode* root) {
        if (root == NULL) return true;
        bool left = isValidBST(root->left);

        if (pre != NULL && pre->val >= root->val) return false;
        pre = root; // 记录前一个节点

        bool right = isValidBST(root->right);
        return left && right;
    }
};

230.二叉搜索树中第k小的元素

给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。

示例 1:

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

思路:中序遍历把二叉搜索树转变为有序数组

class Solution {
public:
    vector<int>vec;  # 定义一个数组用来存储节点数值
    void traversal(TreeNode* root){
        if (root==nullptr)return;
        traversal(root->left);
        vec.push_back(root->val);  # 中序遍历转变为有序数组
        traversal(root->right);
    }
    int kthSmallest(TreeNode* root, int k) {
        traversal(root);
        return vec[k-1];   # 注意k是从1开始的,而数组下标从0开始
    }
};

199.二叉树的右视图

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

示例 1:

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

示例 2:

输入: [1,null,3]
输出: [1,3]

示例 3:

输入: []
输出: []

思路:层序遍历二叉树,每一层的最后一个元素就是二叉树的最右侧节点。

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*>que;
        vector<int>result;
        if(root!=nullptr)que.push(root);
        while(!que.empty()){
            int size=que.size();
            vector<int>vec;
            while(size--){
                TreeNode*node=que.front();
                que.pop();
                vec.push_back(node->val);
                if(node->left)que.push(node->left);
                if(node->right)que.push(node->right);
            }
            int a=vec.size();
            result.push_back(vec[a-1]);
        }
        return result;
    }
};

精简版: 

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        vector<int> result;
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                if (i == (size - 1)) result.push_back(node->val); // 将每一层的最后元素放入result数组中
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return result;
    }
};

114.二叉树展开为链表

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

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

示例 1:

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

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [0]
输出:[0]

思路:如果根节点有左子树,那么根节点的右指针应该指向左子树(即应该把根节点的左子树放在根节点的右子树的位置上)那么之前根节点的右子树怎么办?应该把根节点之前的右子树放到根节点左子树的最右节点的右指针上,然后把根节点的左指针指为空,此时根节点只有右子树,左子树为空,然后接着同样的方式处理根节点的下一个节点(即根节点的右节点)

步骤:

1.判断根节点是否为空

2.根节点不为空时,判断根节点的左子树是否为空。如果根节点的左子树不为空,找到左子树的最右节点,让该节点的右指针指向根节点的右子树(也就是把整个根节点的右子树变成这个节点的右子树)

3.然后根节点的右指针指向根节点的左子树(即把左子树放到右子树的位置上),根节点的左子树置空

4.继续处理根节点的下一个节点

class Solution {
public:
    void flatten(TreeNode* root) {
        TreeNode* cur=root;
        while(cur){
            if(cur->left){
                TreeNode* p=cur->left;
                while(p->right)p=p->right;
                p->right=cur->right;
                cur->right=cur->left;
                cur->left=nullptr;
            }
            cur=cur->right;
        }
    }
};

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

 先做106.从中序与后序遍历序列构造二叉树 

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这棵二叉树 。

思路:

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

递归步骤:
第一步:如果数组大小为零的话,说明是空节点了。

第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

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

第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组),中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割就可以了

第五步:切割后序数组,切成后序左数组和后序右数组。

后序数组没有明确的切割元素来进行左右切割,不像中序数组有明确的切割点,切割点左右分开就可以了。

此时有一个很重的点,就是中序数组大小一定是和后序数组的大小相同的(这是必然)。

中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组

第六步:递归处理左区间和右区间

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def buildTree(self, inorder, postorder):
# 第一步: 特殊情况讨论: 树为空. (递归终止条件)
        if not postorder:
           return None
 
 # 第二步: 后序遍历的最后一个就是当前的中间节点.
        rootvalue=postorder[-1] 
        root=TreeNode(rootvalue)
 
# 第三步: 找切割点.
        separate_index=inorder.index(rootvalue) #使用index方法在中序数组中找到中间节点的索引
        
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
        left_inorder=inorder[:separate_index]#切片操作,从索引0到索引separate_index的值,左闭右开
        right_inorder=inorder[separate_index+1:]
 
# 第五步: 切割postorder数组. 得到postorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的.
        left_postorder=postorder[:len(left_inorder)]
        right_postorder=postorder[len(left_inorder):len(postorder)-1]
 
# 第六步: 递归
        root.left=self.buildTree(left_inorder,left_postorder)
        root.right=self.buildTree(right_inorder,right_postorder)
 
# 第七步: 返回答案
        return root

1.注意确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。

2.使用 index 方法查找 root_val 在 inorder 中的索引,即根节点在中序数组中的位置

3.解释切片操作:

[3:5]: 切片符号表示要包含的索引范围。在 Python 中,起始索引是包含的,而结束索引是不包含的。因此,这个切片操作表示要包括索引 3 到(不包括)5 的元素。

假设 postorder 是 [4, 5, 2, 6, 7, 3, 1]。如果执行 postorder_right = postorder[3:5],那么 postorder_right 将包含值 [6, 7],因为这些是 postorder 数组中索引 3 和 4 的元素(包括索引 3,但不包括索引 5)

然后解105:

根据一棵树的前序遍历与中序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        # 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件
        if not preorder:
            return None
 
        # 第二步: 前序遍历的第一个就是当前的中间节点.
        root_val = preorder[0]
        root = TreeNode(root_val)
 
        # 第三步: 找切割点.
        separator_idx = inorder.index(root_val)
 
        # 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
        inorder_left = inorder[:separator_idx]
        inorder_right = inorder[separator_idx + 1:]
 
        # 第五步: 切割preorder数组. 得到preorder数组的左,右半边.
        # ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的.
        preorder_left = preorder[1:1 + len(inorder_left)]
        preorder_right = preorder[1 + len(inorder_left):]
 
        # 第六步: 递归
        root.left = self.buildTree(preorder_left, inorder_left)
        root.right = self.buildTree(preorder_right, inorder_right)
        # 第七步: 返回答案
        return root

437.路径总和III

给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。

路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

示例 1:

输入:root = [10,5,-3,3,2,null,11,3,-2,null,1], targetSum = 8
输出:3
解释:和等于 8 的路径有 3 条,如图所示。

示例 2:

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

这道题的特别之处在于,每条路径不一定是从根节点开始的,树的每一个节点都可以作为path的起始节点。(双重递归)

class Solution {
public:
    int rootSum(TreeNode* cur,long targetSum){
        int ans=0;
        if(cur==nullptr)return 0;
        if(cur->val==targetSum)ans++;
        ans+=rootSum(cur->left,targetSum-cur->val);
        ans+=rootSum(cur->right,targetSum-cur->val);
        return ans;
    }
    int pathSum(TreeNode* root, int targetSum) {
        if(root==nullptr)return 0;  
        int result=0;
        result+=rootSum(root,targetSum);
        result+=pathSum(root->left,targetSum);
        result+=pathSum(root->right,targetSum);
        return result;

    }
};

解释:

  1. 首先,我们调用 pathSum 函数,并将根节点 和目标值 作为参数传入。这时程序执行以下步骤:

    • 当前根节点不为空,继续执行。
    • 调用 rootSum 函数统计以当前节点(根节点)为起始点的子树中路径和等于目标值 的个数。这时程序执行以下步骤:
      • 当前节点的值等于目标值,ans 加一。
      • 递归调用 rootSum(root->left, 目标值-当前节点值) 统计左子树中路径和满足条件的个数。
      • 递归调用 rootSum(root->right, 目标值-当前节点值) 统计右子树中路径和满足条件 的个数。
    • 左子树递归调用 pathSum 函数,传入左子树根节点 和目标值继续统计。
    • 右子树递归调用 pathSum 函数,传入右子树根节点 和目标值继续统计。
  2. 继续按照上述步骤分析左右子树,直到叶子节点(root==nullptr)。

  3. 最终,程序返回二叉树中路径和等于目标值的个数。

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 。
因为根据定义最近公共祖先节点可以为节点本身。

示例 3:

输入:root = [1,2], p = 1, q = 2
输出:1

首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。 即情况一:

判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。

但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。 情况二:

其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。

因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root==q||root==p)return root;
        if (root==NULL) return NULL;
        TreeNode* left=lowestCommonAncestor(root->left,p,q) ;
        TreeNode* right=lowestCommonAncestor(root->right,p,q);
        if (left!=NULL&&right!=NULL)return root; # 如果left 和 right都不为空,说明此时root就是最近公共节点。
        if (left==NULL&&right!=NULL)return right; # 如果left为空,right不为空,就返回right,说明目标节点是通过right返回的
        else if (left!=NULL&&right==NULL)return left;
        else{return NULL;}
    }
};

总结:

1.求最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。

2.在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值