LeetCode刷题小记 七、【二叉树(一)】

1.二叉树

文章目录

    • 1.二叉树
    • 写在前面
      • 1.1二叉树理论基础
      • 1.2二叉树的递归遍历
      • 1.3二叉树的迭代遍历
      • 1.4二叉树的统一迭代法
      • 1.5二叉树的层序遍历
      • 1.6翻转二叉树
      • 1.7对称二叉树
      • 1.8二叉树的最大深度
      • 1.9二叉树的最小深度
      • 1.10完全二叉树的节点个数
      • 1.11平衡二叉树
      • 1.12二叉树的所有路径
      • 1.13左叶子之和
      • 1.14找树左下角的值
      • 1.15路径总和
    • Reference

写在前面

本系列笔记主要作为笔者刷题的题解,所用的语言为Python3,若于您有助,不胜荣幸。

1.1二叉树理论基础

二叉树[binary tree]是一种非线性数据结构,代表根节点叶子节点之间的派生关系。二叉树的基本单元是节点,每个节点包含值、左子节点引用和右子节点引用。

二叉树是存储方式有两种:分别是链式存储、顺序存储。链式存储方式就是使用指针,顺序存储方式就是使用数组。链式存储方式如下,我们可以使用如下的方式来定义一个二叉树的节点

class TreeNode:
    def __init__(self, val:int, left: Optional[TreeNode]=None, right: Optional[TreeNode]=None):
        self.val: int = val                     # 节点值
        self.left: Optional[TreeNode] = left    # 左引用
        self.right: Optional[TreeNode] = right   # 右引用

顺序存储,我们可以使用一个数组来保存二叉树中节点的值,当我们需要查询一个节点的左右子节点的值时,我们使用当前的索引i2*i+1即左子节点的索引,2*i+2即右子节点的索引。

二叉树的常见术语:

  • 根节点root node:位于二叉树顶层的节点,没有父节点
  • 叶节点leaf node:没有子节点的节点,其两个指针均指向None
  • 边edge:连接两个节点的线段,即节点的引用
  • 边所在的层level:从顶至底递增,根节点所在的层为1
  • 节点的度degree:节点的子节点数量,在二叉树中,度的取值范围为0、1、2
  • 二叉树的高度height:从根节点到最远叶子节点所经过的边的数量
  • 节点的深度depth:任意一个节点到根节点的距离
  • 节点的高度height:任意一个节点到叶子节点的距离

在我们解题的过程中,二叉树主要有两种主要的形式:满二叉树完全二叉树

满二叉树:如果一棵二叉树只有度为0的节点和度为2的节点,并且度为0的节点在同一层上,那么这棵二叉树就被称为满二叉树,如图所示:

Image

完全二叉树:在完全二叉树中,除了最底层的节点可能没有填满外,其余每层节点数都达到了最大值,并且最下面一层的节点都集中在该层最左边的若干位置,若最底层为第 h h h层( h h h从1开始),则该层包含 1 ∼ 2 ( h − 1 ) 1\sim 2^{(h-1)} 12(h1)个节点,如图所示:

Image

二叉搜索树:二叉搜索树引入了数值关系,二叉搜索树是一个有序树:

  • 若它的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;
  • 若它的右子树不为空,则右子树上所有节点的值均大于它的根节点的值;
  • 它的左,右子树也分别为二叉搜索树。

二叉搜索树其实符合一种中序遍历的方式,如图所示:

Image

平衡二叉树:是指二叉树中任意节点的左子树和右子树的高度之差的绝对值不超过1。

平衡二叉搜索树:平衡二叉搜索树又被称为AVL(Adelson-Velsky and Landis)树,且具有以下的性质,

  • 它是一棵空树或者它的左右两个子树的高度差的绝对值不超过1;
  • 它的两个左右子树都是一棵平衡二叉树。

如果一棵树既是二叉搜索树又是平衡二叉树,那么它就是一棵平衡二叉搜索树,如图所示:

Image

二叉树的遍历方式:二叉树主要有两种遍历方式

  1. 深度优先遍历:先往深度走,遇到叶子节点再往回走
  2. 广度优先遍历:逐层去遍历

这两种遍历方式其实是图论中的基本遍历方式,从深度优先遍历和广度优先遍历进一步拓展,则有:

  • 深度优先遍历
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  • 广度优先遍历
    • 层次遍历(迭代法)

前序、中序、后序遍历是一种基于深度优先的搜索方式,它体现了一种“先走到尽头,再回溯继续”的思想,通常使用递归来进行实现。

1.2二叉树的递归遍历

递归算法的三要素:

  • 确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
  • 确定终止条件:写完了递归算法,在运行时,经常会遇到栈溢出的错误,这就是因为我们没有确定递归的终止条件,操作系统也是用一个栈结构来保存每一层递归的信息,如果递归没有终止,那么操作系统的这个栈必然会溢出。
  • 确定单层递归的逻辑:确定每一层递归需要处理的信息,在这里也就是重复调用自身来实现递归的过程。

三道题目分别是:

144. 二叉树的前序遍历

解法一:常规写法

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        def recurse(cur: Optional[TreeNode], res: List[int]) -> None:       # 不直接返回结果,而使用一个数组来保存结果
            if cur==None:
                return
            res.append(cur.val)
            recurse(cur.left, res)
            recurse(cur.right, res)

        ans: List[int] = []
        recurse(root, ans)
        return ans

解法二:Pythonic

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:         # 递归直接返回结果,不使用数组来保存,
        if root == None:
            return []
        mid: List[int] = [root.val]
        left: List[int] = self.preorderTraversal(root.left)
        right: List[int] = self.preorderTraversal(root.right)
        return mid + left + right

中序遍历和后序遍历比较类似,这里就不给出所有代码了。

145. 二叉树的后序遍历

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root==None:
            return []
        mid: List[int] = [root.val]
        left: List[int] = self.postorderTraversal(root.left)
        right: List[int] = self.postorderTraversal(root.right)
        return left + right + mid

94. 二叉树的中序遍历

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        if root == None:
            return []
        mid: List[int] = [root.val]
        left: List[int] = self.inorderTraversal(root.left)
        right: List[int] = self.inorderTraversal(root.right)
        return left + mid + right

1.3二叉树的迭代遍历

递归的实现是:每一次递归调用都会把函数的局部变量、参数值和返回值等压入调用栈中。使用迭代来遍历二叉树,其实就是手动使用一个栈,用迭代的方式来进行遍历。

这里特别需要注意的一点是,以前序遍历为例:中左右,当我们处理完节点的时候,我们需要再处理节点和节点,此时的入栈顺序应该是->右->左,因为当我们弹出的时候,这样才符合正确的前序遍历顺序->左->右

144. 二叉树的前序遍历

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        stack: List[Optional[TreeNode]] = []
        ans: List[int] = []
        stack.append(root)
        while stack:
            node: Optional[TreeNode] = stack.pop()
            if node != None:        # 中
                ans.append(node.val)
            else:
                continue
            stack.append(node.right)    # 右
            stack.append(node.left)     # 左
        return ans

145. 二叉树的后序遍历

如何从前序遍历改为后序遍历呢?前序遍历的顺序是中左右,后序遍历的顺序是左右中,我们只需要交换处理的两行,最后再把结果颠倒过来就行了,右左中<->中左右

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        stack: List[Optional[TreeNode]] = []
        ans: List[int] = []
        stack.append(root)
        while stack:
            node: Optional[TreeNode] = stack.pop()
            if node != None:            # 中
                ans.append(node.val)
            else:
                continue
            stack.append(node.left)     # 左
            stack.append(node.right)    # 右
        return ans[::-1]        # 颠倒结果

94. 二叉树的中序遍历

在处理二叉树的中序遍历时,我们不能够在上述代码的基础上就行修改了直接完成二叉树的中序遍历,因为我们在迭代的过程中有两个过程:

  • 处理:将元素放入ans数组中
  • 访问:遍历所有的节点

我们访问前序和后序遍历中节点的时候,处理和访问的节点是一致的都是中间节点,但我们进行中序遍历的时候,访问的节点和处理的节点不是一致的,所以中序遍历的代码和前序和后序遍历的不统一。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans: List[int] = []
        stack: List[Optional[TreeNode]] = []
        cur: Optional[TreeNode] = root
        while cur != None or stack:
            # 先访问底层的左子树
            if cur != None:
                stack.append(cur)
                cur = cur.left
            # 到达最左节点后处理栈顶元素
            else:
                cur = stack.pop()
                ans.append(cur.val)
                cur = cur.right
        return ans

1.4二叉树的统一迭代法

上述我们实现二叉树的前序遍历和后序遍历的方式是较为统一的,但是如果要实现二叉树的中序遍历就不那么统一了,中序遍历完全是另一种风格了,针对上述的三种遍历方式,我们可以采用一种统一的迭代方式来进行遍历。

为了处理这种访问的元素和要处理的元素不一致的情况,那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记,这样我们就可以使用一个栈来进行三种顺序的遍历了,那么如何做标记呢?就是要处理的节点放入栈之后,紧接着放一个空指针作为标记

144. 二叉树的前序遍历

class Solution:
    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans: List[int] = []
        stack: List[Optional[TreeNode]] = []
        node: Optional[TreeNode] = None
        if root:                            # 对None指针进行剪枝
            stack.append(root)
        while stack:
            node = stack.pop()
            if node != None:
                if node.right:              # 右
                    stack.append(node.right)
                if node.left:               # 左
                    stack.append(node.left)
                stack.append(node)          # 中
                stack.append(None)          # 标记位
            else:
                node = stack.pop()
                ans.append(node.val)
        return ans

145. 二叉树的后序遍历

class Solution:
    def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans: List[int] = []
        stack: List[Optional[TreeNode]] = []
        node: Optional[TreeNode] = None
        if root:
            stack.append(root)
        while stack:
            node = stack.pop()
            if node != None:        
                stack.append(node)              # 中
                stack.append(None)
                if node.right != None:          
                    stack.append(node.right)    # 右
                if node.left != None:
                    stack.append(node.left)     # 左
            else:
                node = stack.pop()
                ans.append(node.val)
        return ans

94. 二叉树的中序遍历

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        ans: List[int] = []
        stack: List[Optional[TreeNode]] = []
        node: Optional[TreeNode] = None
        if root:
            stack.append(root)
        while stack:
            node = stack.pop()
            if node != None:
                if node.right != None:              # 右
                    stack.append(node.right)
                stack.append(node)                  # 中
                stack.append(None)
                if node.left != None:               # 左
                    stack.append(node.left)
            else:
                node = stack.pop()
                ans.append(node.val)
        return ans

1.5二叉树的层序遍历

102. 二叉树的层序遍历

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

思路:对于层序遍历,我们为了知道哪些节点是在同一层,我们需要使用一个队列来保存访问到的节点,并且使用一个size变量来记录当前层中的节点数量,这样我们就能进行层序遍历了。

from collections import deque
class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
        que: deque = deque()
        if not root:        # 避免传入空指针
            return []
        que.append(root)
        ans: List[List[int]] = []
        while que:
            size = len(que)
            res: List[int] = []
            while size:
                node: Optional[TreeNode] = que.popleft()
                res.append(node.val)
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
                size -= 1
            ans.append(res)
        return ans

对于二叉树的层序遍历,还有十道相关的题目,这里分别列举出来:

107. 二叉树的层序遍历 II

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

思路:这道题只需要在返回的时候对结果进行一个翻转即可。

from collections import deque
class Solution:
    def levelOrderBottom(self, root: Optional[TreeNode]) -> List[List[int]]:
        que: deque = deque()
        if not root:            # 避免传入空指针
            return []
        que.append(root)
        ans: List[List[int]] = []
        while que:
            size: int = len(que)
            res: List[int] = []
            while size:
                node: Optional[TreeNode] = que.popleft()
                res.append(node.val)
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
                size -= 1
            ans.append(res)
        return ans[::-1]

199. 二叉树的右视图

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

思路:本题的思路也是层序遍历,只不过需要我们保存右侧的视图,我们只需要保存每次层序遍历的最后一个值即可。

from collections import deque
class Solution:
    def rightSideView(self, root: Optional[TreeNode]) -> List[int]:
        que: deque = deque()
        if not root:
            return []
        ans: List[int] = []
        que.append(root)
        while que:
            size: int = len(que)
            while size:
                node: Optional[TreeNode] = que.popleft()
                if size==1:
                    ans.append(node.val)
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
                size -= 1
        return ans

637. 二叉树的层平均值

给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。

from collections import deque
class Solution:
    def averageOfLevels(self, root: Optional[TreeNode]) -> List[float]:
        que: deque = deque()
        if not root:
            return [0]
        ans: List[float] = []
        que.append(root)
        while que:
            size: int = len(que)
            l: int = size
            s: float = 0.0
            while size:
                node: Optional[TreeNode] = que.popleft()
                s += node.val
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
                size -= 1
            ans.append(s/l)
        return ans

429. N 叉树的层序遍历

给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。

树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。

# Definition for a Node.
class Node:
    def __init__(self, val=None, children=None):
        self.val: int = val
        self.children: List[Optional[Node]] = children

思路:N叉树的定义如上,这里的self.children的值其实是一个List存放了该节点的所有子节点的,明确这一点之后其实就和层序遍历无异了。

from collections import deque
class Solution:
    def levelOrder(self, root: 'Node') -> List[List[int]]:
        if not root:
            return []
        que: deque = deque()
        ans: List[List[int]] = []
        que.append(root)
        while que:
            size: int = len(que)
            res: List[int] = []
            while size:
                cur: Optional[Node] = que.popleft()
                res.append(cur.val)
                for node in cur.children:
                    if node != None:
                        que.append(node)
                size -= 1
            ans.append(res)
        return ans

515. 在每个树行中找最大值

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

from collections import deque
class Solution:
    def largestValues(self, root: Optional[TreeNode]) -> List[int]:
        if not root:
            return []
        que: deque = deque()
        ans: List[int] = []
        que.append(root)
        while que:
            size: int = len(que)
            temp: List[int] = []
            while size:
                node: Optional[TreeNode] = que.popleft()
                temp.append(node.val)
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
                size -= 1
            ans.append(max(temp))
        return ans

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

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

class Node:
    def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL

初始状态下,所有 next 指针都被设置为 NULL

思路:我们在进行层序遍历的时候,只需要将前一个弹出的节点用pre进行保存,让其的next指针指向当前弹出的节点cur即可

from collections import deque
class Solution:
    def connect(self, root: 'Optional[Node]') -> 'Optional[Node]':
        if not root:
            return root
        que: deque = deque()
        que.append(root)
        while que:
            size: int = len(que)
            pre: Optional[Node] = None
            while size:
                cur: Optional[Node] = que.popleft()
                if pre != None:
                    pre.next = cur
                if size == 1:           # 可省略,因为已经默认设置成None了
                    cur.next = None
                if cur.left != None:
                    que.append(cur.left)
                if cur.right != None:
                    que.append(cur.right)
                size -= 1
                pre = cur
        return root     # 我们修改完毕后,需要返回根节点,供系统判题

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

给定一个二叉树:

class Node:
    def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None):
        self.val = val
        self.left = left
        self.right = right
        self.next = next

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL

初始状态下,所有 next 指针都被设置为 NULL

思路:这道题和上一道题不同的地方在于这道题不是完美二叉树,但对于我们使用层序遍历的方式来说并没有影响,所以,这道题可以和上一题的解法一模一样。

from collections import deque
class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return root
        que: deque = deque()
        que.append(root)
        while que:
            size: int = len(que)
            pre: Optional[Node] = None
            while size:
                cur: Optional[Node] = que.popleft()
                if pre != None:
                    pre.next = cur
                if cur.left != None:
                    que.append(cur.left)
                if cur.right != None:
                    que.append(cur.right)
                size -= 1
                pre = cur
        return root

104. 二叉树的最大深度

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

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

解法一:使用层序遍历

from collections import deque
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root: 
            return 0
        que: deque = deque()
        que.append(root)
        depth: int = 0
        while que:
            size: int = len(que)
            while size:
                node: Optional[TreeNode] = que.popleft()
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
                size -= 1
            depth += 1
        return depth

解法二:使用递归

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

111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

**说明:**叶子节点是指没有子节点的节点。

思路:需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点

from collections import deque
class Solution:
    def minDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        que: deque = deque()
        que.append(root)
        depth: int = 0
        while que:
            depth += 1
            for _ in range(len(que)):
                node: Optional[TreeNode] = que.popleft()
                if node.left == None and node.right == None:	# 遇到叶子节点直接返回值
                    return depth
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
        return depth

1.6翻转二叉树

226. 翻转二叉树

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

解释:翻转的意思就是交换根节点下的左右子树。

解法一:采用递归

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 采用前序遍历
        if not root:
            return root
        root.left, root.right = root.right, root.left   # 中
        self.invertTree(root.left)                      # 左
        self.invertTree(root.right)                     # 右
        return root
    
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 采用后序遍历
        if not root:
            return root
        self.invertTree(root.left)                      # 左
        self.invertTree(root.right)                     # 右
        root.left, root.right = root.right, root.left   # 中
        return root
    
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 采用中序遍历
        if not root:
            return root
        self.invertTree(root.left)                      # 左
        root.left, root.right = root.right, root.left   # 中
        self.invertTree(root.left)                      # 右
        return root

解法二:采用迭代法

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 采用前序遍历
        if not root:
            return root
        stack: List[Optional[TreeNode]] = []
        stack.append(root)
        while stack:
            node: Optional[TreeNode] = stack.pop()          
            node.left, node.right = node.right, node.left   # 中
            if node.left:                                   # 左
                stack.append(node.left)
            if node.right:                                  # 右
                stack.append(node.right)
        return root

class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 采用中序遍历
        if not root:
            return root
        stack: List[Optional[TreeNode]] = []
        stack.append(root)
        while stack:
            node: Optional[TreeNode] = stack.pop()          
            if node.left:                                   # 左
                stack.append(node.left)
            node.left, node.right = node.right, node.left   # 中
            if node.left:                                   # 右
                stack.append(node.left)
        return root
    
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 采用后序遍历
        if not root:
            return root
        stack: List[Optional[TreeNode]] = []
        stack.append(root)
        while stack:
            node: Optional[TreeNode] = stack.pop()          
            if node.left:                                   # 左
                stack.append(node.left)
            if node.right:                                  # 右
                stack.append(node.right)
            node.left, node.right = node.right, node.left   # 中
        return root

解法三:采用层序遍历

from collections import deque
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 前序遍历
        if not root:
            return root
        que: deque = deque()
        que.append(root)
        while que:
            for _ in range(len(que)):
                node: Optional[TreeNode] = que.popleft()
                node.left, node.right = node.right, node.left       # 中
                if node.left:
                    que.append(node.left)                           # 左
                if node.right:
                    que.append(node.right)                          # 右
        return root
    
from collections import deque
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 中序遍历
        if not root:
            return root
        que: deque = deque()
        que.append(root)
        while que:
            for _ in range(len(que)):
                node: Optional[TreeNode] = que.popleft()
                if node.left:
                    que.append(node.left)                           # 左
                node.left, node.right = node.right, node.left       # 中
                if node.left:
                    que.append(node.left)                          # 右
        return root

from collections import deque
class Solution:
    def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        # 后序遍历
        if not root:
            return root
        que: deque = deque()
        que.append(root)
        while que:
            for _ in range(len(que)):
                node: Optional[TreeNode] = que.popleft()
                if node.left:
                    que.append(node.left)                           # 左
                if node.right:
                    que.append(node.right)                          # 右
                node.left, node.right = node.right, node.left       # 中
        return root

1.7对称二叉树

101. 对称二叉树

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

思路:这到题我们只能使用后序遍历的方式,这样我们才能收集左右子树的信息然后返回给上一个子树。当我们需要收集子节点的信息向上一层返回的时候,我们需要使用后序遍历

class Solution:
    def isSymmetric(self, root: Optional[TreeNode]) -> bool:
        def recurse(left: Optional[TreeNode], right: Optional[TreeNode]) -> bool:
            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
            else:
                outside: bool = recurse(left.left, right.right)         # 左
                inside: bool = recurse(left.right, right.left)          # 右
                return outside and inside                               # 中
        return not root or recurse(root.left, root.right)               # 防止传入空指针

1.8二叉树的最大深度

104. 二叉树的最大深度

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

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

  • 节点的深度depth:任意一个节点到根节点的距离
  • 节点的高度height:任意一个节点到叶子节点的距离

对于求取深度,我们可以使用前序遍历,对于求取高度,我们一般使用后序遍历。

解法一:递归

后序遍历求取根节点的高度,那么根节点的高度就是整棵树的最大深度。

class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        # 采用后序遍历
        if not root: 
            return 0
        leftheight: int = self.maxDepth(root.left)      # 左
        rightheight: int = self.maxDepth(root.right)    # 右
        height: int = 1 + max(leftheight, rightheight)  # 中
        return height

下面是后序遍历的精简写法:

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

解法二:迭代

迭代的解法,我们可以使用层序遍历来求解,我们遍历的层数,就是二叉树的最大深度

from collections import deque
class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        que: deque = deque()
        que.append(root)
        depth: int = 0
        while que:
            depth += 1
            for _ in range(len(que)):
                node: Optional[TreeNode] = que.popleft()
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
        return depth

559. N 叉树的最大深度

解法一:递归

class Solution:
    def maxDepth(self, root: 'Node') -> int:
        if not root:
            return 0
        depth: int = 1
        for node in root.children:
            depth = max(depth, self.maxDepth(node)+1)
        return depth

解法二:迭代

from collections import deque
class Solution:
    def maxDepth(self, root: 'Node') -> int:
        if not root:
            return 0
        que: deque = deque()
        que.append(root)
        depth: int = 0
        while que:
            depth += 1
            for _ in range(len(que)):
                node: Optional[Node] = que.popleft()
                for child in node.children:
                    if child:       # 如果child不为空指针
                        que.append(child)
        return depth

1.9二叉树的最小深度

111. 二叉树的最小深度

解法一:递归

思路:注意这里的处理逻辑很容易写成

height: int = 1 + min(leftheight, rightheight)

这样是不行的,因为当我们的节点没有左节点或者右节点的时候,它就直接进行返回了,并不能找到正确的子节点继续遍历直到找到叶子节点。

class Solution:
    def minDepth(self, root: Optional[TreeNode]) -> int:
        # 后序遍历
        if not root:
            return 0
        leftheight: int = self.minDepth(root.left)      # 左
        rightheight: int = self.minDepth(root.right)    # 右
        if root.left != None and root.right == None:    # 中
            return 1 + leftheight
        elif root.left == None and root.right != None:
            return 1 + rightheight
        else:
            height: int = 1 + min(leftheight, rightheight)
        return height

解法二:迭代

from collections import deque
class Solution:
    def minDepth(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        que: deque = deque()
        que.append(root)
        depth: int = 0
        while que:
            depth += 1
            for _ in range(len(que)):
                node: Optional[TreeNode] = que.popleft()
                if node.left == None and node.right == None:        # 遇到第一个叶子节点则返回
                    return depth
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
        return depth

1.10完全二叉树的节点个数

222. 完全二叉树的节点个数

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~2h 个节点。

可以使用普通二叉树的一些解法,包括递归和迭代之类的方法。

解法一:递归法

class Solution:
    def countNodes(self, root: Optional[TreeNode]) -> int:
        # 后序遍历
        if root == None:
            return 0
        left: int = self.countNodes(root.left)      # 左
        right: int = self.countNodes(root.right)    # 右
        count: int = left + right + 1               # 中
        return count

解法二:迭代法

from collections import deque
class Solution:
    def countNodes(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        que: deque = deque()
        que.append(root)
        count: int = 0
        while que:
            for _ in range(len(que)):
                count += 1
                node: Optional[TreeNode] = que.popleft()
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
        return count

除了上述的解法之外,我们还可以利用完全二叉树的特性来进行计算,完全二叉树的节点数量 n n n和其层数之间 l l l满足 n = 2 l − 1 n=2^l - 1 n=2l1,利用这一特性,我们可以判断一个节点的左右子节点是否为完全二叉树,如果为完全二叉树我们就直接返回其节点数量即可,这样加速了递归的过程。

解法三:利用完全二叉树的特性,丰富终止条件

class Solution:
    def countNodes(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        left: Optional[TreeNode] = root.left
        leftdepth: int = 0
        while left:
            left = left.left
            leftdepth += 1
        right: Optional[TreeNode] = root.right
        rightdepth: int = 0
        while right:
            right = right.right
            rightdepth += 1
        if rightdepth == leftdepth:
            return 2**(leftdepth+1) - 1     # leftdepth计算的是该节点的子树的层数,还要再加上该节点,才算是整个完全二叉树子树的层数
        
        leftnodenums: int = self.countNodes(root.left)      # 左
        rightnodenums: int = self.countNodes(root.right)    # 右
        nodenums: int = leftnodenums + rightnodenums + 1    # 中
        return nodenums

1.11平衡二叉树

110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

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

class Solution:
    def isBalanced(self, root: Optional[TreeNode]) -> bool:
        # 后序遍历
        def recurse(root: Optional[TreeNode]) -> int:
            if not root:
                return 0
            leftheight: int = recurse(root.left)        # 左
            if leftheight == -1:
                return -1
            rightheight: int = recurse(root.right)      # 右
            if rightheight == -1:
                return -1
            height: int = max(leftheight, rightheight) + 1 if abs(leftheight - rightheight) <= 1 else -1    # 中
            return height
        return recurse(root) != -1

1.12二叉树的所有路径

257. 二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

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

思路:这道题其实涉及到了回溯的过程,我们在使用递归法的时候要思考怎么才能正确回溯到初始的根节点,然后再次出发。

class Solution:
    def traversal(self, cur: Optional[TreeNode], path: List[int], result: List[str]):
        path.append(cur.val)                        # 中
        if not cur.left and not cur.right:          # 到达叶子节点,则保存结果
            sPath = '->'.join(map(str, path))
            result.append(sPath)
            return 
        if cur.left:                                # 左
            self.traversal(cur.left, path, result)
            path.pop()                              # 主动回溯到上一个节点
        if cur.right:                               # 右
            self.traversal(cur.right, path, result)
            path.pop()

    def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:
        path: List[int] = []
        result: List[str] = []
        if not root:
            return []
        self.traversal(root, path, result)
        return result

1.13左叶子之和

404. 左叶子之和

给定二叉树的根节点 root ,返回所有左叶子之和。

思路:在做这道题之前,我们需要明确左叶子节点的定义。左叶子节点的定义如下,节点A的左孩子不为空,且左孩子的左右孩子都为空(说明为叶子节点),那么节点A的左孩子为左叶子节点

class Solution:
    def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
        if not root:                                        # 遇到空root直接return 0
            return 0
        if root.left == None and root.right == None:        # 遇到叶子节点也return 0,现在还不能确定是否是左叶子
            return 0
        leftval: int = self.sumOfLeftLeaves(root.left)      # 左
        if root.left and root.left.right == None and root.left.left == None:        # 遇到左叶子节点
            leftval = root.left.val
        rightval: int = self.sumOfLeftLeaves(root.right)    # 右
        result: int = leftval + rightval                    # 中
        return result 

1.14找树左下角的值

513. 找树左下角的值

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

解法一:迭代法,层序遍历

from collections import deque
class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        if not root:
            return 0
        que: deque = deque()
        que.append(root)
        result: int = 0
        while que:
            for i in range(len(que)):
                node: Optional[TreeNode] = que.popleft()
                if i == 0:			# 保存最左侧的结果
                    result = node.val
                if node.left:
                    que.append(node.left)
                if node.right:
                    que.append(node.right)
        return result

解法二:递归法

思路:在使用递归法的时候,我们优先遍历左侧的值,无论是前序中序还是后序,这里不涉及到中间节点的处理逻辑,所以前中后序都是可以的,当我们遍历到最大深度的时候,优先查找左侧的值,如果左侧有值则直接返回即可,如果没有值说明我们应该想右侧去寻找。

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        self.max_depth: int = float('-inf')     # 定义全局变量
        self.result: int = None
        self.traversal(root, 0)
        return self.result
        
    def traversal(self, node: Optional[TreeNode], depth: int) -> None:
        if node.left == None and node.right == None:    # 终止条件,找到叶子节点
            if depth > self.max_depth:
                self.max_depth = depth
                self.result = node.val
                return
        if node.left:                           # 左
            depth += 1
            self.traversal(node.left, depth)
            depth -= 1                          # 回溯
        if node.right:                          # 右
            depth += 1
            self.traversal(node.right, depth)
            depth -= 1                          # 回溯

上述代码还可以隐藏回溯的过程做到如下的精简

class Solution:
    def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
        self.max_depth: int = float('-inf')     # 定义全局变量
        self.result: int = None
        self.traversal(root, 0)
        return self.result
        
    def traversal(self, node: Optional[TreeNode], depth: int) -> None:
        if node.left == None and node.right == None:    # 终止条件,找到叶子节点
            if depth > self.max_depth:
                self.max_depth = depth
                self.result = node.val
                return
        if node.left:                           # 左
            self.traversal(node.left, depth+1)  # 回溯
        if node.right:                          # 右
            self.traversal(node.right, depth+1) # 回溯   

1.15路径总和

112. 路径总和

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

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

思路:这道题和257. 二叉树的所有路径的思路基本一致,都可以使用递归来求解。

class Solution:
    def traversal(self, cur: Optional[TreeNode], path: List[int], result: List[int]) -> None:
        path.append(cur.val)
        if cur.left == None and cur.right == None:        # 遇到叶子节点则找到一条完整路径
            result.append(sum(path))
            return
        if cur.left:
            self.traversal(cur.left, path, result)
            path.pop()
        if cur.right:
            self.traversal(cur.right, path, result)
            path.pop()

    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:            # 排除传入空指针
            return False
        res: List[int] = []
        path: List[int] = []
        self.traversal(root, path, res)
        return targetSum in res

上述的实现寻找了所有的路径和,这种思路比较慢,我们其实不需要找到所有的路径和,我们只需要找到一条符合题意的结果之后我们就能立刻返回了。

class Solution:
    def traversal(self, cur: Optional[TreeNode], count: int) -> bool:
        if cur.left == None and cur.right == None and count == 0:
            return True
        if cur.left == None and cur.right == None:
            return False
        if cur.left:
            count -= cur.left.val
            if self.traversal(cur.left, count):
                return True
            count += cur.left.val       # 回溯
        if cur.right:
            count -= cur.right.val
            if self.traversal(cur.right, count):
                return True
            count += cur.right.val      # 回溯
        return False

    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        return self.traversal(root, targetSum-root.val)

上述代码还可以做如下的两种精简

第一种是隐藏回溯

class Solution:
    def traversal(self, cur: Optional[TreeNode], count: int) -> bool:
        if cur.left == None and cur.right == None and count == 0:
            return True
        if cur.left == None and cur.right == None:
            return False
        if cur.left:
            if self.traversal(cur.left, count - cur.left.val): # 回溯
                return True
        if cur.right:
            if self.traversal(cur.right, count - cur.right.val): # 回溯
                return True
        return False

    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        return self.traversal(root, targetSum-root.val)

第二种是无需回溯

class Solution:
    def traversal(self, cur: Optional[TreeNode], count: int) -> bool:
        count -= cur.val                # 把处理逻辑放在这里,我们就不需要再进行回溯了
        if cur.left == None and cur.right == None and count == 0:
            return True
        if cur.left:
            if self.traversal(cur.left, count):
                return True
        if cur.right:
            if self.traversal(cur.right, count):
                return True
        return False

    def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
        if not root:
            return False
        return self.traversal(root, targetSum)

113. 路径总和 II

class Solution:
    def traversal(self, cur: Optional[TreeNode], path: List[int], result: List[List[int]], target: int):
        path.append(cur.val)
        if cur.left == None and cur.right == None:      # 遇到叶子节点
            if sum(path) == target:
                result.append(path[:])                  # 这里需要使用path的拷贝值,不能直接传入path,不然操作的是同一块内存,会被pop清空
                return
        if cur.left:
            self.traversal(cur.left, path, result, target)
            path.pop()                                  # 回溯
        if cur.right:
            self.traversal(cur.right, path, result, target)
            path.pop()                                  # 回溯

    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        if not root:
            return []
        path: List[int] = []
        res: List[List[int]] = []
        self.traversal(root, path, res, targetSum)
        return res
class Solution:
    def traversal(self, cur: Optional[TreeNode], path: List[int], result: List[List[int]], count: int) -> None:
        path.append(cur.val)
        count -= cur.val
        if cur.left == None and cur.right == None:
            if count == 0:
                result.append(path[:])
                return
        if cur.left:                                        # 左
            self.traversal(cur.left, path, result, count)
            path.pop()                                      # 回溯
        if cur.right:                                       # 右
            self.traversal(cur.right, path, result, count)
            path.pop()                                      # 回溯

    def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
        if not root:
            return []
        path: List[int] = []
        result: List[int] = []
        self.traversal(root, path, result, targetSum)
        return result

Reference

[1] Hello 算法
[2] 代码随想录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/431706.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年软考-官方最新考试安排出来了,软考新调整,很重要,但也很惹人气愤

官方最新通知&#xff0c;关于2024年度计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试工作计划 笔试改机考后&#xff0c;必然会迎来调整&#xff0c;但有点让人费解。 这次调整变动主要是每年考试的次数调整&#xff0c;很多改为了一年一考&#xff0c;具体…

宠物的异味,用空气净化器可以解决吗?宠物空气净化器品牌推荐

养猫的人都了解&#xff0c;一个养猫家庭的环境卫生和气味问题与主人的关系密切相关。主人的勤劳程度和对卫生的重视程度直接影响着家中的气味。尽管主人通常会经常更换猫砂&#xff0c;但有时候仍然会存在一些难闻的气味。事实上&#xff0c;忙碌的猫主人可能会因为没有足够的…

安装RabbitMQ及配置Centos7 方式(2)

1、背景需求 自行搭建学习参考使用&#xff0c;这里采用的Centos7 方式&#xff0c;这已经是多年前的方式了&#xff0c;现在主流方式是容器化安装、部署&#xff0c;docker、ks8&#xff0c;同学们可自行去学习参考。 2、搭建环境 环境&#xff1a;centos7 、otp_src_21.3、…

Day09:基础入门-算法逆向散列对称非对称JS源码逆向AESDESRSASHA

目录 算法加密-概念&分类&类型 加密解密-识别特征&解密条件 解密实例-密文存储&数据传输 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&am…

(二)数据库系统的结构抽象与演变

待补充 2.1三层模式与两层映像&#xff0c;物理独立性和逻辑独立性 从数据角度可以分为三层视图模式默认指的是全局模式&#xff0c;视图默认指的是外部视图 一个数据库只有一个内模式 DBMS要让用户定义三层模式&#xff0c;程序自动地实现两层映像 。 2.2数据→模式→数据模型…

C#程序模块的封装

文章目录 一、简单认识程序模块的封装1.1什么情况下使用封装&#xff1f;1.2 具体的例子 二、实际当中的程序封装的应用DLL的主要特点和用途&#xff1a;如何在C#中创建和使用DLL&#xff1a; 一、简单认识程序模块的封装 在C#中&#xff0c;程序模块的封装&#xff08;Encaps…

数据结构中红黑树的概念以及代码

红黑树&#xff08;Red-Black Tree&#xff09;是一种自平衡的二叉搜索树&#xff0c;它在插入和删除节点时通过一系列的旋转和重新着色操作来保持平衡。红黑树的平衡性质使得它的查找、插入和删除操作的时间复杂度都能保持在 O(log n) 红黑树的定义如下&#xff1a; 每个节点要…

qt cmake添加resource文件

文章目录 方式一:方式二:qrc的使用 两种方式 方式一: 创建一个qrc文件&#xff0c;在qt_add_executable 中直接添加 qt_add_executable(helloworldmain.cppimageresources.qrc )方式二: 使用 qt_add_resources qt_add_resources(helloworld "app_images"PREFIX &…

dolphinscheduler海豚调度(四)钉钉告警

在之前的博文中&#xff0c;我们已经介绍了DolphinScheduler海豚调度的基本概念和工作流程&#xff0c;以及Shell任务和SQL任务的实践。今天&#xff0c;让我们来学习DolphinScheduler中的另一个重要功能&#xff1a;钉钉告警。 钉钉群添加机器人 在钉钉群添加机器人&#xf…

三国野史秘闻翻译视频剪辑 条条爆品 一条视频增粉1w (附888G素材内容)

我将为大家分享一个全新的主题——三国野史秘闻。这个主题本身就充满了趣味性&#xff0c;再加上我们独特的解读&#xff0c;由于粉丝们对此类内容非常热衷&#xff0c;因此很容易在评论区引发热烈讨论&#xff0c;这使得我们的短视频有很大的机会在抖音上走红。 项目 地 址 &…

基于springboot的学生网上请假系统设计与实现论文

学生网上请假系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了学生网上请假系统的开发全过程。通过分析学生网上请假系统管理的不足&#xff0c;创建了一个计算机管理学生网上请假系统的方案。文章介绍了学…

React富文本编辑器开发(六)

现在&#xff0c;相关的基础知识我们应该有个大概的了解了&#xff0c;但离我们真正的开发出一个实用型的组件还有一段距离&#xff0c;不过不用担心&#xff0c;我们离目标已经越来越近。 以现在我们所了解的内容而言&#xff0c;或许你发现了一个问题&#xff0c;就是我们的编…

ICASSP2024 | ICMC-ASR 车载多通道语音识别挑战赛总结

为促进驾驶场景中语音处理和识别研究&#xff0c;在ISCSLP 2022上成功举办智能驾驶座舱语音识别挑战 (ICSRC)的基础上&#xff0c;西工大音频语音与语言处理研究组 (ASLPNPU)联合理想汽车、希尔贝壳、WeNet社区、字节、微软、天津大学、南洋理工大学以及中国信息通信研究院等多…

splay学习笔记重制版

以前写的学习笔记&#xff1a;传送门 但是之前写的比较杂乱&#xff0c;这里重制一下 问题背景 假设我们要维护一个数据结构&#xff0c;支持插入、删除、查询某个值的排名&#xff0c;查询第 k k k大的值等操作。 最直接的想法是用二叉搜索树&#xff0c;也就是左子树权值&l…

Tomcat实现java博客项目、状态页及常见配置介绍

目录 一、自建博客 1. 项目背景 2. 操作示例 二、状态页 1. 概述 2. server status 信息状态页 3. manager app 项目管理状态页 4. host manger 虚拟主机管理状态页 三、常见配置 1. 端口8005/tcp安全配置管理 2. tomcat端口号 3. 虚拟主机设置 4. Context配置 一…

2024年第一届CS2major,新胶囊即将发行,需要提前做哪些布局

2024年第一届CS2major&#xff0c;将会在3月17日哥本哈根开始。 所以&#xff1a; 1、新的胶囊大概率会在3月10日左右发布。 2、网传战队挂坠&#xff0c;不知道是否会出现&#xff1f;&#xff08;原本出现过战队布章包&#xff0c;由于销量太差&#xff0c;第二届就取消了…

【Qt】Qwidget的常见属性

目录 一、Qwidget核心属性 二、enable属性 三、geometry属性 四、 WindowFrame的影响 五、windowTitle属性 六、windowIcon属性 七、qrc文件管理资源 八、windowOpacity属性 九、cursor属性 十、font属性 十一、toolTip属性 十二、focusPolicy属性 十三、styleShe…

Mysql面试总结

基础 1. 数据库的三范式是什么&#xff1f; 第一范式&#xff1a;强调的是列的原子性&#xff0c;即数据库表的每一列都是不可分割的原子数据项。第二范式&#xff1a;要求实体的属性完全依赖于主关键字。所谓完全 依赖是指不能存在仅依赖主关键字一部分的属性。第三范式&…

redis09 集群(cluster)

思维草图 为什么要使用集群 单台redis内存容量的限制单台redis并发写量太大有性能瓶颈 redis集群认识 redis集群是对redis的水平扩容&#xff0c;即启动N个redis节点&#xff0c;将整个数据分布存储在这个N个节点中&#xff0c;每个节点存储总数据的1/N。 如下图&#xff1…

LVS负载均衡集群+NAT部署

一. LVS集群相关知识 1. 集群和分布式 系统性能扩展方式&#xff1a; Scale UP&#xff1a;垂直扩展&#xff0c;向上扩展,增强&#xff0c;性能更强的计算机运行同样的服务 升级单机的硬件设备 Scale Out&#xff1a;水平扩展&#xff0c;向外扩展,增加设备&#xff0c;并行…
最新文章