S'S ALGORITHM

深度优先搜索dfs相关问题


适用范围

问题1:Diameter of Binary Tree

这个问题要求计算二叉树的直径。二叉树的直径定义为树中任意两个节点之间的最长路径的长度。这条路径可能经过根节点,也可能不经过根节点。

实现步骤:

代码实现:

def diameter_of_binaryTree(root):
    maxDiameter = 0
    
    def dfs(node):
        nonlocal maxDiameter
        if node is None:
            return 0
        leftDepth = dfs(node.left)
        rightDepth = dfs(node.right)
        # 更新直径
        maxDiameter = max(maxDiameter, leftDepth + rightDepth)
        # 返回当前节点的深度
        return max(leftDepth, rightDepth) + 1
    
    dfs(root)
    return maxDiameter

学习笔记:如果是暴力破解法,则需要对每一个节点进行计算,它到树中其他节点的最远距离,然后追踪距离,这会让时间复杂度达到n的平方次。使用上面的这种方法,时间复杂度则为n,因为所有的节点只需要遍历一次。

问题2:Serialize and Deserialize Binary Tree

其实我一点都不懂序列化是什么意思,所以先查一下概念吧。

“Serialize and Deserialize Binary Tree” 是一种将二叉树序列化(转换为字符串表示)和反序列化(从字符串表示还原为二叉树)的算法。这种算法通常用于将二叉树存储到文件或者通过网络传输。

序列化(Serialize):是将二叉树转换为字符串表示的过程。在序列化过程中,我们需要将二叉树的结构和节点值转换为一个字符串。通常使用先序遍历(Pre-order Traversal)的方式来实现序列化,即按照根节点-左子树-右子树的顺序遍历二叉树,并将节点的值转换为字符串,并使用特定的符号(例如逗号或空格)来分隔节点值。如果节点为 null,则用特定的标记例如 “#”表示。

反序列化(Deserialize):是将字符串表示的二叉树转换回原始的二叉树结构的过程。在反序列化过程中,我们需要解析字符串,并将其转换为二叉树的结构和节点值。通常采用递归的方式进行反序列化。我们首先解析字符串中的第一个节点值,并创建一个节点。然后递归地调用反序列化函数来构建左子树和右子树,直到所有节点都被处理完毕。

比如考虑以下二叉树:

    1
   / \
  2   3
     / \
    4   5

序列化的结果可能是:”1,2,#,#,3,4,#,#,5,#,#”。

这种算法通过将二叉树转换为字符串表示,实现了二叉树的持久化存储和传输。这种算法具有简单高效的特点,适用于需要将二叉树保存到文件或者通过网络传输的场景。

解题思路:

serialize:

deserialize 函数:

# Definition of a binary tree node
#
# class TreeNode:
#     def __init__(self, data):
#         self.data = data
#         self.left = None
#         self.right = None

from ds_v1.BinaryTree.BinaryTree import TreeNode

def serialize(root):
    """将二叉树序列化为字符串表示"""
    if not root:
        return "#,"  # 空节点用 "#" 表示
    
    # 序列化当前节点值,以及左右子树
    left_serialized = serialize(root.left)
    right_serialized = serialize(root.right)
    
    return str(root.data) + "," + left_serialized + right_serialized

def deserialize(stream):
    """将字符串表示反序列化为二叉树"""
    def helper(stream):
        val = next(stream)  # 从迭代器中取出一个值
        if val == "#":
            return None
        node = TreeNode(int(val))
        node.left = helper(stream)
        node.right = helper(stream)
        return node
    
    stream = iter(stream.split(","))  # 将字符串分割成列表,并创建一个迭代器
    return helper(stream)

以上是自编和修改,以下是官方题解:他加入了一个M的maker,同时带有编号,也许带这个编号在某些场景下有好的作用,整体上方法大同小异,都是通过递归的方法,将左右树枝分别进行处理和添加。

# Initializing our marker
MARKER = "M"
m = 1

def serialize_rec(node, stream):
    global m

    if node is None:
        stream.append(MARKER + str(m))
        m += 1
        return

    stream.append(node.data)

    serialize_rec(node.left, stream)
    serialize_rec(node.right, stream)

# Function to serialize tree into list of integers.
def serialize(root):
    stream = []
    serialize_rec(root, stream)
    return stream

def deserialize_helper(stream):
    val = stream.pop()

    if type(val) is str and val[0] == MARKER:
        return None

    node = TreeNode(val)

    node.left = deserialize_helper(stream)
    node.right = deserialize_helper(stream)

    return node

# Function to deserialize integer list into a binary tree.
def deserialize(stream):
    stream.reverse()
    node = deserialize_helper(stream)
    return node

学习笔记:同样的,树的算法加上递归的算法,体现的是一种递归的美,让人欲罢不能。这道题在时间复杂度上,因为遍历了所有的节点,所以是O(n),空间复杂度分情况,因为是递归的从上到下经历了整个树的高度,所以应该是O(height),如果是平衡的二叉树,就是对数时间,如果是不平衡的,最大可以是O(n)。

问题3:Binary Tree Maximum Path Sum

题目的意思是给定一个二叉树,要求找到一条路径,使得该路径上的节点值之和最大。这条路径可以从二叉树的任意节点出发,沿着父节点、左子节点和右子节点之间的连接线移动,并且每个节点最多只能被访问一次。要求找到一条最大路径和是指从某个节点出发,经过该节点的路径上所有节点值之和,是所有路径和中的最大值。

以下是代码的步骤方法总结:

代码:

# Definition of a binary tree node
# class TreeNode:
#     def __init__(self, data):
#         self.data = data
#         self.left = None
#         self.right = None

from ds_v1.BinaryTree.BinaryTree import TreeNode

def max_path_sum(root):
    max_sum = float('-inf')

    def helper(node):
        nonlocal max_sum

        if not node:
            return 0
        
        # 计算左右子树的最大路径和,不考虑负数
        left_sum = max(0, helper(node.left))
        right_sum = max(0, helper(node.right))
        
        # 更新全局的最大和
        max_sum = max(max_sum, node.data + left_sum + right_sum)
        
        # 返回以当前节点为根节点的子树的最大路径和
        return node.data + max(left_sum, right_sum)

    helper(root)
    return max_sum

学习笔记:每次我拿到题,感觉最难的地方,在于梳理题目的意思,每次在理解题意上要花的时间,远远大于之后的步骤。这样正说明了,解决问题的时候,对问题的理解至关重要。

一开始我没有理解helper函数中最后的return的部分,为什么加号后面是选取最大的那个,想清楚后理解了,这里返回的东西,要在下一轮中,在left_sum和right_sum中使用,他们要作为递归后的一个分叉来使用,因为是一个分支,所以当然是选择最大的那个。很多时候我都是困在一个地方。

时间复杂度是O(n),空间复杂度是树的高度。