刷题记录8---验证二叉搜索树+二叉树的层序遍历+从前序与中序遍历序列构造二叉树+二叉树展开为链表+二叉树的最近公共祖先

本文介绍了如何利用递归和队列解决验证二叉搜索树和二叉树的层序遍历问题。在验证二叉搜索树时,通过递归检查节点值的范围,确保每个子树都符合搜索树特性。而在进行层序遍历时,利用队列逐层收集节点值。此外,还展示了如何从先序和中序遍历序列构建二叉树,以及如何将二叉树展开为链表。最后,讲解了找到二叉树中两个节点最近公共祖先的方法,通过遍历节点路径找到最近公共祖先。
摘要由CSDN通过智能技术生成

前言

所有题目均来自力扣题库中的hot 100,之所以要记录在这里,只是方便后续复习

98.验证二叉搜索树

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

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

节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
验证二叉搜索树示意图

输入:root = [2,1,3]
输出:true
示例 2:
输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
提示:
树中节点数目范围在[1, 104] 内
-231 <= Node.val <= 231 - 1
解题思路:
【递归】根据二叉搜索树的性质:我们要先判断左子树值小于根节点值,右子树值大于根节点值,符合条件的话我们要在判断左子数是否是搜索二叉树,右子树是否为搜索二叉树,因为判断方式一样,这就很容易联想到递归;那递归的中止条件是什么呢?当节点为空时,直接放回True即可;那我们在递归的时候每个根节点的值是会变的,其左子树 和 右子树值的范围也会跟着改变,所以要把这个范围当作参数传入递归函数,当递归左子数时要更新最大值为根节点值,递归右子树时要更新最小值为根节点值
代码(python):

# 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 isValidBST(self, root):
        """
        :type root: TreeNode
        :rtype: bool
        """
        # 定义初始的最新小值 和 最大值,注意不是int类型最值了,是正负无穷
        low = float("-inf")
        high = float("inf")
        # 调用递归方法
        return self.test(root, low, high)

    def  test(self, root, low, high):
        #如果当前节点为空,直接返回True即可
        if root is None:
            return True
        # 如果当前节点 值超过了 最小值 和 最大值的范围,直接返回False
        if root.val >= high or root.val <= low:
            return False
        # 递归验证左子数和右子树是否为搜索二叉树
        # 注意要更新最小值或最大值的范围 左子数的最大值变成节点值  右子树的最小值变成节点值
        return self.test(root.left, low, root.val) and self.test(root.right, root.val , high)

代码(java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        ArrayList<Integer> values = new ArrayList<>();
        zhongXu(root, values);
        return isSorted(values);
    }

    public void zhongXu(TreeNode root, ArrayList<Integer> values){
        if(root == null){
            return;
        }
        zhongXu(root.left, values);
        values.add(root.val);
        zhongXu(root.right, values);
    }

    public boolean isSorted(ArrayList<Integer> values){
        for(int i = 0; i < values.size() - 1; i++){
            if (values.get(i) >= values.get(i + 1)){
                return false;
            }
        }
        return true;
    }
}

知识点:

  • python中,可以用float(“inf”)和float(“-inf”)表示正负无穷,而sys.maxsize和-sys.maxsize - 1表示最大int值和最小int值
  • java中,可以用Long.MAX_VALUE和Long.MIN_VALUE表示Long类型的最大最小值,而Integer.MAX_VALUE和Integer.MIN_VALUE表示int类型的最大最小值

原题链接:验证二叉搜索树

102.二叉树的层序遍历

题目:
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
示例 1:
二叉树的层序遍历

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
解题思路:
【队列】首先想到的思路是先用节点集合把一层的节点存储起来,然后将一层节点值取出来组成一个集合并添加到结果集合中,再把这个集合里的节点替换成下一层节点,可是这样要频繁删除节点集合的值,进一步优化我们可以用队列存储节点,这样边将本层节点依次出队列将值放到集合中,边将该节点的子节点入队列,那这种怎么知道哪些节点是本层的,哪些节点是下一层的呢?可以在每层出队列开始时(此时下一层节点还未入列队)计算一下队列长度,只按这个长度进行出队列,这样就都是本层节点了
代码(python):

# 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 levelOrder(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[int]]
        """
        results = []
        self.test(root, results, 0)
        return results

    def test(self, root, results, level):
        if root is None:
            return
        
        if level >= len(results):
            results.append([root.val])
        else:
            results[level].append(root.val)
        
        self.test(root.left, results, level + 1)
        self.test(root.right, results, level + 1)

代码(java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        //定义 结果集合
        ArrayList<List<Integer>> result = new ArrayList<>();
        // 如果当前节点为空,返回空集合即可
        if(root == null){
            return result;
        }
        //定义队列,用来存放每层的节点
        Queue<TreeNode> myQueue = new LinkedList<>();
        //先把根节点放入队列中
        myQueue.offer(root);
        //从队列中取值,直到队列为空
        while(myQueue.isEmpty() == false){
            // 先计算一下当前队列的长度,即计算一下这一层有多少个节点,用来作层与层之间的分割
            int curSize = myQueue.size();
            // 定义当前集合
            ArrayList<Integer> curList = new ArrayList<>();
            // 把这一层的节点依次拿出,并将节点的值添加到当前集合中,再将左节点和右节点添加都队列中用于下一层的遍历,注意判断左右节点是否为空
            for(int i = 0; i < curSize; i ++){
                TreeNode curNode = myQueue.poll();
                curList.add(curNode.val);
                if (curNode.left != null){
                    myQueue.offer(curNode.left);
                }
                if (curNode.right != null){
                    myQueue.offer(curNode.right);
                }
            }
            // 将当前集合添加到结果集合中
            result.add(curList);
        }
        return result;
    }
}

知识点:

  • java中可以用LinkedList当作队列,linkedlist.offer(val)表示入队列,linkedlist.poll()表示出队列,linkedlist.isEmpty()判断对列是否为空
  • python中可以用Queue()定义队列,但是需要导报from queue import Queue,入队列put(val),出队列get()

原题链接:二叉树的层序遍历

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

题目:
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例 1:
从前序与中序遍历序列构造二叉树

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
示例 2:
输入: preorder = [-1], inorder = [-1]
输出: [-1]
解题思路:
【递归】二叉树的题可以先考虑递归,我们先确定根节点,然后确定左子数和右子树有哪些节点,再用递归去构造左右子树即可;怎么确定根节点?根据先序遍历的顺序:根-左-右,先序遍历序列第一个值即为根节点的值;怎么确定左子数和右子树有哪些呢?根据中序遍历的顺序:左-根-右,我们在中序遍历序列中找到根节点,那他左边的就是左子数的节点,右边的就是右子树的节点,我们可以计算出左右子树的节点个数,根据左右子树的个数我们就可以把先序和中序序列进行有效的切分并进行左右子树的递归,与其进行序列的切分倒不如用左右边界值控制有效范围,减少时间复杂度;那递归的中止条件是什么呢?即左右子树个数为0的即左右边界值无效的情况,返回空节点即可
代码(python):

# 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):

    inorderIndex = {}

    def buildTree(self, preorder, inorder):
        """
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: TreeNode
        """
        if preorder is None or len(preorder) == 0:
            return None
        
        self.getInorderIndex(inorder)
 
        return self.myBuildTree(preorder, inorder, 0, len(preorder) - 1, 0, len(inorder) - 1)

    def myBuildTree(self, preorder, inorder, preorder_left, preorder_right, inorder_left, inorder_right):
        """
        递归方法
        通过先序遍历的第一个位置确定出根节点
        通过根节点在中序遍历中的位置,左侧部分为左子树,右侧部分为右子树
        分别将左子树和右子树通过递归的方式进行构造数,并赋给根节点的左右节点
        """
        #如果先序遍历列表的左边界大于右边界,直接返回
        if preorder_left > preorder_right:
            return
        #如果先序遍历列表的左边界等于右边界,说明只有一个节点,该节点没有子节点,直接新建节点赋值返回
        if preorder_left == preorder_right:
            return TreeNode(preorder[preorder_left])
        #前序遍历的第一个值即左边界值就是 根节点,创建根节点
        root = TreeNode(preorder[preorder_left])
        #通过根节点的值在中序遍历中找到该位置
        index = self.inorderIndex[preorder[preorder_left]]
        #计算出左子树的节点个数
        left_number = index - inorder_left
        #递归得到左子树并赋给根节点  通过左子树的个数得到左子树前序遍历的边界范围,通过根节点在中序遍历的位置得到左子树的中序遍历的边界范围
        root.left = self.myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + left_number, inorder_left, index - 1)
         #递归得到右子树并赋给根节点  通过左子树的个数得到右子树前序遍历的边界范围,通过根节点在中序遍历的位置得到右子树的中序遍历的边界范围
        root.right = self.myBuildTree(preorder, inorder, preorder_left + left_number + 1, preorder_right,index + 1, inorder_right)

        return root


    def getInorderIndex(self, inorder):
        """
        获取到中序遍历数组的索引与节点的对应关系 --节点:索引 
        放到属性字典里
        """
        for i in range(0, len(inorder)):
            self.inorderIndex[inorder[i]] = i

代码(java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        //调用递归函数
      return test(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    public TreeNode test(int[] preorder, int pl, int pr, int[] inorder, int il, int ir){
        // 当先序遍历的左边界值大于右边界 或 中序遍历的左边界大于右边界,说明当前树点为空
        if(pl > pr || il > ir){
            return null;
        }
        // 当先序遍历或中序遍历的左边界和右边界相等,说明,当前树只有一个节点,直接构造一个节点返回即可
        if(pl == pr || il == ir ){
            return new TreeNode(preorder[pl]);
        }
        //先序遍历有效范围第一位即根节点  根据先序遍历的性质
        int rootValue = preorder[pl];
        TreeNode root = new TreeNode(rootValue);
        // 在中序遍历有效位范围内找到根节点,
        int rootIndex = il;
        for (int i = il; i <= ir; i ++){
            if(inorder[i] == rootValue){
                rootIndex = i;
                break;
            }
        }
        //定义左右子树的节点个数   因为中序遍历有效范围内根节点左边的节点都是左子数,右边的节点都是右子树
        int leftNum = rootIndex - il;
        int rightNum = ir - rootIndex;
        // 通过递归的方式构造左右子树并赋给根节点 通过控制有效范围达到传递子树的先序遍历和中序遍历效果
        root.left = test(preorder, pl + 1, pl + leftNum, inorder, il, rootIndex - 1);
        root.right = test(preorder, pl + leftNum + 1, pl + leftNum + rightNum, inorder, rootIndex + 1, ir);
        return root;
    }
}

知识点:

原题链接:从前序与中序遍历序列构造二叉树

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]
解题思路:
【链表遍历】由于和先序遍历顺序相同,我们可以先序遍历二叉树,将节点存放在列表里,然后将他们拼成链表,但这样空间复杂度为On;要想空间复制度为O1,需要原地操作,将其展开为单链表,其实就是将所有左子数插入右子树对应的位置,我们可以遍历二叉树,如果有左子树,就让其插入右子树中;怎么插入右子树呢,我们先将右子树挂在左子数上,挂在哪里呢?根据先序遍历的顺序,右子树的前一个节点应该是左子数的最右节点,就挂在最右节点上,然后将左子数移到原右子树的位置,这样就完成了左子数的插入,注意的是原左子数要置为空
代码(python):

# 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):

    nodes = []

    def flatten(self, root):
        """
        :type root: TreeNode
        :rtype: None Do not return anything, modify root in-place instead.
        """

        if root is None:
            return None

        self.pre(root)

        head = self.nodes[0]
        tail = head

        for i in range(0, len(self.nodes) - 1):
            tail.left = None
            tail.right = self.nodes[i + 1]
            tail = tail.right
        
        return head


    def pre(self, root):
        if root is None:
            return
        
        self.nodes.append(root)
        self.pre(root.left)
        self.pre(root.right)

代码(java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left; 
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public void flatten(TreeNode root) {
       //定义索引节点
       TreeNode cur  = root;
       // 遍历怎个二叉树
       while(cur != null){
           //如果存在左子数
            if (cur.left != null){
                //将其左子树存起来
                TreeNode next = cur.left;
                // 找到左子树的最右节点 即右子树的前节点
                TreeNode pre = next;
                while (pre.right != null){
                    pre = pre.right;
                }
                //将 右子树挂在前节点上(也是左子树)
                pre.right = cur.right;
                // 将当前节点的左子数 置为空
                cur.left = null;
                // 将当前节点右子树 置为 原来存的左子树
                cur.right = next;
            }
            // 滑动索引节点 (包括左子数处理完的情况 与 左子数为空不需要处理的情况)
            cur = cur.right;
       }

    }
}

知识点:

原题链接:二叉树展开为链表

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

解题思路:
【链表遍历】我们可以从两个节点向上查找其到根节点路径,第一个相交的节点便是最近公共祖先;但是向上查找需要知道各个节点的父节点怎么办?我们可以先遍历一遍二叉树,通过字典(子节点-父节点)的存储各个节点的父节点;这样我们可以先用列表存储一个节点路径(要保证有序,不能用hash结构),在寻找第二个节点的路径时判断是否在第一个路径中,若在则该节点就是相交节点即结果值
代码(python):

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):


    def lowestCommonAncestor(self, root, p, q):
        """
        :type root: TreeNode
        :type p: TreeNode
        :type q: TreeNode
        :rtype: TreeNode
        """
        # 定义字典,用来存储每个节点的父节点
        parents = dict()
        self.get_parents(root, parents)
        # 根据字典,从p节点开始到根节点,依次把父节点放入列表中
        p_list = list()
        while p:
            p_list.append(p)
            p = parents.get(p, None)
        # 根据字典,从q节点开始遍历其父节点,第一个在列表中节点就是最近公共祖先
        while q not in p_list:
            q = parents.get(q, None)
        return q

    def get_parents(self, root, parents):
        # 如果节点为空 直接返回
        if root is None:
            return
        # 如果有左节点,将左节点的父节点放入字典中
        if root.left:
            parents[root.left] =  root
        # 如果有右节点,将右节点的父节点放入字典中
        if root.right:
            parents[root.right] = root
        # 递归寻找出左右子数的各个节点的父节点
        self.get_parents(root.left, parents)
        self.get_parents(root.right, parents)        

代码(java):

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {

    public HashMap<Integer, TreeNode> parents = new HashMap<>();
    
    public void dfs(TreeNode root){
        if (root.left != null){
            this.parents.put(root.left.val, root);
            dfs(root.left);
        }
        if (root.right != null){
            this.parents.put(root.right.val, root);
            dfs(root.right);
        }
    }

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        dfs(root);
        ArrayList<Integer> pList = new ArrayList<>();

        while(p.val != root.val){
            pList.add(p.val);
            p = this.parents.get(p.val);
        }
        pList.add(p.val);

        while(q.val != root.val){
            if (pList.contains(q.val)){
                return q;
            }
            q = this.parents.get(q.val);
        }

        return root;
    }
}

知识点:

  • python中可以用dict.get(key, default),从字典中获取值,若没有该键则返回default值

原题链接:二叉树的最近公共祖先

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值