力扣刷题记录&整理——(七)Trees


前言

整理力扣刷题思路。

  • 语言:python
  • 题库:来自neetcode: link

一、预备知识

二叉树

二叉树是一种非常基本的数据结构,在计算机科学中经常会用到。想象一棵倒挂的树,二叉树就是从一个顶点开始,向下生长的一棵树。这个顶点我们称之为根节点,它就像是树的起点一样。

每个节点都可以有两个分支,也就是说,每个节点最多可以有两个子节点。这两个子节点分别被称为“左子节点”和“右子节点”。如果节点没有子节点,我们就说这个节点是一个“叶子节点”,因为它在整棵树的最末端。

二叉树的遍历是指按照某种顺序访问树中的每个节点的过程,确保每个节点都被访问一次。具体到前序遍历、中序遍历和后序遍历,它们的区别在于父节点和子节点的访问顺序。

想象一下有这样一棵二叉树:

    A
   / \
  B   C
 / \   \
D   E   F

这棵树有三层,最顶上的是根节点A,它下面有两个子节点B和C。B节点下面又有两个子节点D和E,而C节点下面有一个子节点F。

前序遍历(Pre-order Traversal)

前序遍历是先访问父节点,然后是左子节点,最后是右子节点。对于上面这棵树的前序遍历结果是:先访问根节点A,然后访问A的左子节点B,接着是B的左子节点D,完成了B的子节点遍历后,我们回到B,然后访问B的右子节点E。完成左侧所有节点后,我们回到根节点A,然后访问A的右子节点C,最后访问C的右子节点F。

因此,前序遍历的结果是:A B D E C F。

中序遍历(In-order Traversal)

中序遍历是先访问左子节点,然后是父节点,最后是右子节点。我们还是以同样的树作为例子,中序遍历先从根节点A开始,但是不是立即访问A,而是先访问它的左子节点B,接着再访问B的左子节点D。因为D没有子节点,我们访问D之后,中序遍历返回到它的父节点B,并访问B。之后是B的右子节点E。遍历完B之后,我们返回到A,并访问A,然后是A的右子节点C,最后是C的右子节点F。

因此,中序遍历的结果是:D B E A C F。

后序遍历(Post-order Traversal)

后序遍历先访问子节点,然后是父节点。如果一个节点有两个子节点,会先访问左子节点,然后是右子节点,最后是父节点。使用同样的示例树来演示,后序遍历开始于根节点A,但我们先下移至左子节点B,然后再下移至B的左子节点D。由于D没有子节点,我们访问D,然后返回到B,但在访问B之前,我们需要访问B的右子节点E。只有访问了E之后,我们才能访问B。之后,我们回到A,并下移至A的右子节点C,同样地,我们先访问C的子节点F,然后才是C。最后访问根节点A。

所以后序遍历的结果是:D E B F C A。


二、解题思路

1.DFS

226.invert-binary-tree

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

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root:
            return root
        
        root.left,root.right = self.invertTree(root.right),self.invertTree(root.left)  
        return root

104.maximum-depth-of-binary-tree

给定一个二叉树 root ,返回其最大深度。

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

link

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0      
        return 1+max(self.maxDepth(root.left), self.maxDepth(root.right))

100.same-tree

给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
link

class Solution:
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        if not p and not q:
            return True
        if not p or not q:
            return False
        
        return p.val == q.val and self.isSameTree(p.left,q.left) and self.isSameTree(p.right,q.right)

572.subtree-of-another-tree

给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。

二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
link

class Solution:
    def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        if not subRoot:
            return True
        if not root:
            return False

        return self.sameTree(root,subRoot) or self.isSubtree(root.left,subRoot) or self.isSubtree(root.right,subRoot)


    def sameTree(self,p,q):
        if not p and not q:
            return True
        if not p or not q:
            return False
        
        return p.val==q.val and self.sameTree(p.left,q.left) and self.sameTree(p.right,q.right)

235.lowest-common-ancestor-of-a-binary-search-tree

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

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

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if p.val<root.val and q.val<root.val:
            return self.lowestCommonAncestor(root.left,p,q)
        if p.val>root.val and q.val>root.val:
            return self.lowestCommonAncestor(root.right,p,q)
        return root

参考:link

543.diameter-of-binary-tree

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

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

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

class Solution:
    def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
        self.cnt = 1
        def lenTree(root):
            if not root:
                return 0
            l,r = lenTree(root.left),lenTree(root.right)
            #cnt保留的是当前最长路径的节点数,所以最后结果要减1
            self.cnt = max(self.cnt, l+r+1)
            #返回当前根节点往下的某一侧最长路径节点数,+1是+当前根节点
            return max(l,r)+1
        
        lenTree(root)
        return self.cnt-1

参考:link

110.balanced-binary-tree

给定一个二叉树,判断它是否是 平衡二叉树(平衡二叉树 是指该树所有节点的左右子树的深度相差不超过 1。)
link

class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        
        self.out = True
        def depth(root):
            if not root:
                return 0
            l,r = depth(root.left),depth(root.right)
            if not -1<=l-r<=1:
                self.out = False
            #此处的返回值是从当前节点往下的最大深度
            return max(l,r)+1
        
        depth(root)
        return self.out

这题跟上一题求树的直径做法基本相同,也是每次都要比较左右子树

1448.count-good-nodes-in-binary-tree

给你一棵根为 root 的二叉树,请你返回二叉树中好节点的数目。

「好节点」X 定义为:从根到该节点 X 所经过的节点中,没有任何节点的值大于 X 的值。
link

class Solution:
    def goodNodes(self, root: TreeNode, mx=float('-inf')) -> int:
        if not root:
            return 0
        
        #cnt指示当前节点是否是好节点
        cnt = 0
        if root.val>=mx:
            cnt = 1
            mx = root.val
        #总的好节点=左子树的+右子树的+当前节点
        return self.goodNodes(root.left,mx) + self.goodNodes(root.right,mx) + cnt

199.binary-tree-right-side-view

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

class Solution:
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        ans = []

        def dfs(root,depth):
            if not root:
                return None
            #维护深度保证每次只更新当前深度看到的最右侧的节点值
            if depth==len(ans):
                ans.append(root.val)
            #这两个递归保证了将最右侧一直看到底部才会继续向左看
            dfs(root.right, depth+1)
            dfs(root.left, depth+1)
        
        dfs(root,0)
        return ans

98.validate-binary-search-tree

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

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

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

class Solution:
    def isValidBST(self, root: Optional[TreeNode], low=float('-inf'), high=float('inf')) -> bool:
        if not root:
            return True
        #维护每个节点的上下边界
        if not low<root.val<high:
            return False
        
        return self.isValidBST(root.left,low,root.val) and self.isValidBST(root.right,root.val,high)

230.kth-smallest-element-in-a-bst/description

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

class Solution:
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        self.ans = root.val

        def dfs(root):
            #一直到达最左端的树的底部
            #或者计数已经为0,则无需再遍历直接返回
            if not root or self.k == 0:
                return
            dfs(root.left)

            #每经过一个节点将计数减1
            self.k -= 1
            if self.k==0:
                self.ans = root.val
            dfs(root.right)
        
        self.k = k
        dfs(root)
        return self.ans

参考:link

105.construct-binary-tree-from-preorder-and-inorder-traversal

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
link

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> Optional[TreeNode]:
        hashmap = {}
        for idx,order in enumerate(inorder):
            hashmap[order] = idx
        
        #pre_idx是当前子树在前序遍历的起始位置
        #in_l, in_r是当前子树在中序遍历的起始、结束位置
        def dfs(pre_idx, in_l, in_r):
            if in_l>in_r:
                return
            root = TreeNode(preorder[pre_idx])
            idx = hashmap[preorder[pre_idx]]

            root.left = dfs(pre_idx+1, in_l, idx-1)
            root.right = dfs(pre_idx+1+idx-in_l, idx+1, in_r)
            return root
        
        return dfs(0,0,len(inorder)-1)

参考:link

124.binary-tree-maximum-path-sum

二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和 。
link

class Solution:
    def maxPathSum(self, root: Optional[TreeNode]) -> int:
        self.max = root.val

        def dfs(root):
            if not root:
                return 0
            l = max(dfs(root.left),0)
            r = max(dfs(root.right),0)

            #经过当前节点的最大路径和
            cur_max = root.val+l+r
            self.max = max(self.max, cur_max)

            #函数的返回值是经过当前节点的最大单侧路径和
            return max(root.val+l, root.val+r)
        
        dfs(root)
        return self.max

2.BFS

226.invert-binary-tree

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        if not root:
            return root
        
        stack = [root]
        while stack:
            tmp = stack.pop(0)
            #先左右交换,再入栈
            #这样每次弹出的第一位都是按翻转后顺序排列的
            tmp.left,tmp.right = tmp.right,tmp.left
            if tmp.left:
                stack.append(tmp.left)
            if tmp.right:
                stack.append(tmp.right)
        return root

104.maximum-depth-of-binary-tree

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0      
        
        stack = [root]
        cnt = 0
        while stack:
            tmp = []
            for node in stack:
                if node.left:
                    tmp.append(node.left)
                if node.right:
                    tmp.append(node.right)
            #每进入while一次,就将stack更新为当前深度所有节点
            stack = tmp
            cnt += 1
        return cnt

100.same-tree

class Solution:
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        if not p and not q:
            return True
        if not p or not q:
            return False
        
        q1,q2 = [p],[q]
        while q1 and q2:
            tmp1,tmp2 = q1.pop(0),q2.pop(0)
            if tmp1.val != tmp2.val:
                return False
            
            l1,r1 = tmp1.left,tmp1.right
            l2,r2 = tmp2.left,tmp2.right
            if (not l1)^(not l2) or (not r1)^(not r2):
                return False
            
            if l1:
                q1.append(l1)
            if r1:
                q1.append(r1)
            if l2:
                q2.append(l2)
            if r2:
                q2.append(r2) 
        return not q1 and not q2

572.subtree-of-another-tree

class Solution:
    def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
        new = [root]

        while new:
            node = new.pop(0)
            if node.val == subRoot.val:
                if self.sametree(node, subRoot):
                    return True
            
            if node.left:
                new.append(node.left)
            if node.right:
                new.append(node.right)
            
        return False


    def sameTree(self,p,q):
        if not p and not q:
            return True
        if not p or not q:
            return False
        
        return p.val==q.val and self.sameTree(p.left,q.left) and self.sameTree(p.right,q.right)

102.binary-tree-level-order-traversal

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

class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        if not root:
            return []

        ans = []
        stack = [root]
        while stack:
            tmp = []
            for _ in range(len(stack)):
                node = stack.pop(0)
                #tmp储存当前深度所有节点的值
                tmp.append(node.val)

                #stack加上下一深度的所有节点
                #for循环会pop出当前深度的所有节点,结束后刚好只剩下下一深度的节点
                if node.left:
                    stack.append(node.left)
                if node.right:
                    stack.append(node.right)
                    
            ans.append(tmp)
        return ans

这一题的内容就是做BFS,注意结果返回的是每个节点的值,不是节点

98.validate-binary-search-tree

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        #维护每个节点的上下边界
        stack = [(root, float('-inf'), float('inf'))]
        while stack:
            node,low,high = stack.pop(0)
            if not low<node.val<high:
                return False
            
            if node.left: stack.append((node.left, low, node.val))
            if node.right: stack.append((node.right, node.val, high))
        return True

297.serialize-and-deserialize-binary-tree

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

link

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        if not root:
            return "[]"
        
        stack = [root]
        ans = []
        while stack:
            node = stack.pop(0)
            if node:
                ans.append(str(node.val))
                stack.append(node.left)
                stack.append(node.right)
            else:
                ans.append('null')
        return ','.join(ans)

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        if data=='[]':
            return

        val = data.split(',')
        root = TreeNode(int(val[0]))

        stack,idx = [root],1
        while stack:
            node = stack.pop(0)
            if val[idx] != 'null':
                node.left = TreeNode(int(val[idx]))
                stack.append(node.left)
            idx += 1
            if val[idx] != 'null':
                node.right = TreeNode(int(val[idx]))
                stack.append(node.right)
            idx += 1
        return root
        

# Your Codec object will be instantiated and called as such:
# ser = Codec()
# deser = Codec()
# ans = deser.deserialize(ser.serialize(root))

参考:link


3.迭代

235.lowest-common-ancestor-of-a-binary-search-tree

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

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

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        while root:
            #根据二叉树的特性,向下搜索子树
            if p.val<root.val and q.val<root.val:
                root = root.left
            elif p.val>root.val and q.val>root.val:
                root = root.right
            else:
                return root
  • 30
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,关于力扣刷题C++常用操作,我可以给你一些常见的操作和技巧: 1. 使用 STL 容器和算法库:STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,包含了许多常用的容器和算法。在力扣刷题中,使用 STL 可以大大提高代码的效率和可读性。例如,vector 可以用来存储动态数组,sort 可以用来排序等等。 2. 使用 auto 关键字:auto 关键字可以自动推导变量类型,可以减少代码量和提高可读性。例如,auto x = 1; 可以自动推导出 x 的类型为 int。 3. 使用 lambda 表达式:lambda 表达式是 C++11 中引入的一种匿名函数,可以方便地定义一些简单的函数对象。在力扣刷题中,使用 lambda 表达式可以简化代码,例如在 sort 函数中自定义比较函数。 4. 使用位运算:位运算是一种高效的运算方式,在力扣刷题中经常会用到。例如,左移运算符 << 可以用来计算 2 的幂次方,右移运算符 >> 可以用来除以 2 等等。 5. 使用递归:递归是一种常见的算法思想,在力扣刷题中也经常会用到。例如,二叉树的遍历、链表的反转等等。 6. 使用 STL 中的 priority_queue:priority_queue 是 STL 中的一个容器,可以用来实现堆。在力扣刷题中,使用 priority_queue 可以方便地实现一些需要维护最大值或最小值的算法。 7. 使用 STL 中的 unordered_map:unordered_map 是 STL 中的一个容器,可以用来实现哈希表。在力扣刷题中,使用 unordered_map 可以方便地实现一些需要快速查找和插入的算法。 8. 使用 STL 中的 string:string 是 STL 中的一个容器,可以用来存储字符串。在力扣刷题中,使用 string 可以方便地处理字符串相关的问题。 9. 注意边界条件:在力扣刷题中,边界条件往往是解决问题的关键。需要仔细分析题目,考虑各种边界情况,避免出现错误。 10. 注意时间复杂度:在力扣刷题中,时间复杂度往往是评判代码优劣的重要指标。需要仔细分析算法的时间复杂度,并尽可能优化代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值