动态规划进阶:树形DP深度解析

1. 树形DP概述

树形DP是动态规划在树形结构上的应用,通过递归遍历树,在子树上进行动态规划。这类问题通常需要后序遍历(自底向上)来计算每个节点的状态值。

2. 树形DP基本概念

2.1 树形DP特点

  • 树形结构:基于树或二叉树结构
  • 递归性质:父节点的解依赖于子节点的解
  • 无后效性:子树之间相互独立
  • 自底向上:通常采用后序遍历

2.2 通用模板

python 复制代码
def tree_dp(root):
    """
    树形DP通用模板
    """
    # 空节点返回基础值
    if not root:
        return base_result
    
    # 递归处理左右子树
    left_result = tree_dp(root.left)
    right_result = tree_dp(root.right)
    
    # 根据左右子树的结果计算当前节点的结果
    result = combine_results(root.val, left_result, right_result)
    
    return result

3. 经典树形DP问题

3.1 二叉树中的最大路径和 (LeetCode 124)

问题描述:路径可以是任意节点到任意节点,不一定经过根节点。

状态定义
  • max_gain(node):以node为起点的最大路径和
  • 全局变量 max_sum 记录全局最大路径和
思路分析

对于每个节点:

  1. 计算左子树的最大贡献值(如果为负则取0)
  2. 计算右子树的最大贡献值(如果为负则取0)
  3. 当前节点的最大路径和 = 节点值 + 左贡献 + 右贡献
  4. 更新全局最大值
  5. 返回以当前节点为起点的最大路径和 = 节点值 + max(左贡献, 右贡献)
Python实现
python 复制代码
class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

def maxPathSum(root):
    """
    二叉树中的最大路径和
    """
    def max_gain(node):
        nonlocal max_sum
        
        if not node:
            return 0
        
        # 递归计算左右子树的最大贡献值
        left_gain = max(max_gain(node.left), 0)  # 如果为负,则取0
        right_gain = max(max_gain(node.right), 0)
        
        # 当前节点的最大路径和(可能包含左右子树)
        price_newpath = node.val + left_gain + right_gain
        
        # 更新全局最大值
        max_sum = max(max_sum, price_newpath)
        
        # 返回以当前节点为起点的最大路径和
        return node.val + max(left_gain, right_gain)
    
    max_sum = float('-inf')
    max_gain(root)
    return max_sum

#### 面向对象版本
class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        self.max_sum = float('-inf')
        self._dfs(root)
        return self.max_sum
    
    def _dfs(self, node):
        if not node:
            return 0
        
        # 计算左右子树的最大贡献
        left_max = max(self._dfs(node.left), 0)
        right_max = max(self._dfs(node.right), 0)
        
        # 当前节点的最大路径和
        curr_sum = node.val + left_max + right_max
        self.max_sum = max(self.max_sum, curr_sum)
        
        # 返回以当前节点为起点的最大路径和
        return node.val + max(left_max, right_max)
Java实现
java 复制代码
public class BinaryTreeMaximumPathSum {
    int maxSum = Integer.MIN_VALUE;
    
    public int maxPathSum(TreeNode root) {
        maxGain(root);
        return maxSum;
    }
    
    private int maxGain(TreeNode node) {
        if (node == null) {
            return 0;
        }
        
        // 计算左右子树的最大贡献值
        int leftGain = Math.max(maxGain(node.left), 0);
        int rightGain = Math.max(maxGain(node.right), 0);
        
        // 当前节点的最大路径和
        int priceNewpath = node.val + leftGain + rightGain;
        
        // 更新全局最大值
        maxSum = Math.max(maxSum, priceNewpath);
        
        // 返回以当前节点为起点的最大路径和
        return node.val + Math.max(leftGain, rightGain);
    }
}

3.2 打家劫舍 III (LeetCode 337)

问题描述:房屋形成二叉树,不能抢劫直接相连的房屋。

状态定义

返回一个长度为2的数组:

  • res[0]:不抢劫当前房屋的最大金额
  • res[1]:抢劫当前房屋的最大金额
Python实现
python 复制代码
def rob_III(root):
    """
    打家劫舍 III - 树形DP
    """
    def dfs(node):
        """
        返回:[不抢当前节点的最大收益, 抢当前节点的最大收益]
        """
        if not node:
            return [0, 0]
        
        left = dfs(node.left)
        right = dfs(node.right)
        
        # 不抢当前节点:子节点可抢可不抢,取最大值
        not_rob = max(left[0], left[1]) + max(right[0], right[1])
        
        # 抢当前节点:子节点不能抢
        rob = node.val + left[0] + right[0]
        
        return [not_rob, rob]
    
    result = dfs(root)
    return max(result[0], result[1])

#### 使用namedtuple提高可读性
from collections import namedtuple

RobResult = namedtuple('RobResult', ['not_rob', 'rob'])

def rob_III_namedtuple(root):
    def dfs(node):
        if not node:
            return RobResult(0, 0)
        
        left = dfs(node.left)
        right = dfs(node.right)
        
        # 不抢当前节点
        not_rob = max(left.not_rob, left.rob) + max(right.not_rob, right.rob)
        
        # 抢当前节点
        rob = node.val + left.not_rob + right.not_rob
        
        return RobResult(not_rob, rob)
    
    result = dfs(root)
    return max(result.not_rob, result.rob)
Java实现
java 复制代码
public class HouseRobberIII {
    public int rob(TreeNode root) {
        int[] result = dfs(root);
        return Math.max(result[0], result[1]);
    }
    
    // 返回数组:[不抢当前节点的最大收益, 抢当前节点的最大收益]
    private int[] dfs(TreeNode node) {
        if (node == null) {
            return new int[]{0, 0};
        }
        
        int[] left = dfs(node.left);
        int[] right = dfs(node.right);
        
        // 不抢当前节点
        int notRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
        
        // 抢当前节点
        int rob = node.val + left[0] + right[0];
        
        return new int[]{notRob, rob};
    }
}

3.3 二叉树的直径 (LeetCode 543)

问题描述:二叉树中两个节点之间最长路径的长度(可能不经过根节点)。

思路分析

直径 = 左子树深度 + 右子树深度

对于每个节点,计算:

  1. 左子树深度
  2. 右子树深度
  3. 当前节点的直径 = 左深度 + 右深度
  4. 更新全局最大直径
  5. 返回以当前节点为根的深度 = 1 + max(左深度, 右深度)
Python实现
python 复制代码
def diameterOfBinaryTree(root):
    """
    二叉树的直径
    """
    def depth(node):
        nonlocal diameter
        
        if not node:
            return 0
        
        left_depth = depth(node.left)
        right_depth = depth(node.right)
        
        # 更新直径:当前节点的直径 = 左深度 + 右深度
        diameter = max(diameter, left_depth + right_depth)
        
        # 返回当前节点的深度
        return 1 + max(left_depth, right_depth)
    
    diameter = 0
    depth(root)
    return diameter

#### 类方法版本
class DiameterCalculator:
    def __init__(self):
        self.diameter = 0
    
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        self._depth(root)
        return self.diameter
    
    def _depth(self, node):
        if not node:
            return 0
        
        left_depth = self._depth(node.left)
        right_depth = self._depth(node.right)
        
        # 更新直径
        self.diameter = max(self.diameter, left_depth + right_depth)
        
        # 返回深度
        return 1 + max(left_depth, right_depth)
Java实现
java 复制代码
public class DiameterOfBinaryTree {
    int diameter = 0;
    
    public int diameterOfBinaryTree(TreeNode root) {
        depth(root);
        return diameter;
    }
    
    private int depth(TreeNode node) {
        if (node == null) {
            return 0;
        }
        
        int leftDepth = depth(node.left);
        int rightDepth = depth(node.right);
        
        // 更新直径
        diameter = Math.max(diameter, leftDepth + rightDepth);
        
        // 返回深度
        return 1 + Math.max(leftDepth, rightDepth);
    }
}

4. 复杂树形DP问题

4.1 监控二叉树 (LeetCode 968)

问题描述:在二叉树节点上安装摄像头,每个摄像头可以监控自身、父节点和直接子节点,求最少需要多少摄像头。

状态定义

每个节点有三种状态:

  • 0: 未被监控
  • 1: 有摄像头
  • 2: 被监控但无摄像头
状态转移
python 复制代码
def minCameraCover(root):
    """
    监控二叉树
    """
    # 全局摄像头数量
    cameras = [0]
    
    def dfs(node):
        """
        返回节点的状态
        0: 未被监控
        1: 有摄像头
        2: 被监控但无摄像头
        """
        if not node:
            # 空节点视为被监控(不需要在其上安装摄像头)
            return 2
        
        left = dfs(node.left)
        right = dfs(node.right)
        
        # 情况1:左右子节点都未被监控
        if left == 0 or right == 0:
            cameras[0] += 1
            return 1  # 当前节点需要安装摄像头
        
        # 情况2:左右子节点至少有一个有摄像头
        if left == 1 or right == 1:
            return 2  # 当前节点被监控
        
        # 情况3:左右子节点都被监控但无摄像头
        return 0  # 当前节点未被监控
    
    # 根节点状态
    root_state = dfs(root)
    
    # 如果根节点未被监控,需要安装摄像头
    if root_state == 0:
        cameras[0] += 1
    
    return cameras[0]

#### 更清晰的实现
class CameraSolution:
    def minCameraCover(self, root: TreeNode) -> int:
        self.result = 0
        
        # 定义三种状态
        UNCOVERED = 0    # 未被监控
        CAMERA = 1       # 有摄像头
        COVERED = 2      # 被监控但无摄像头
        
        def dfs(node):
            if not node:
                return COVERED
            
            left = dfs(node.left)
            right = dfs(node.right)
            
            # 如果子节点有未被监控的,当前节点必须安装摄像头
            if left == UNCOVERED or right == UNCOVERED:
                self.result += 1
                return CAMERA
            
            # 如果子节点有摄像头,当前节点被监控
            if left == CAMERA or right == CAMERA:
                return COVERED
            
            # 子节点都被监控但无摄像头
            return UNCOVERED
        
        # 检查根节点是否需要摄像头
        if dfs(root) == UNCOVERED:
            self.result += 1
        
        return self.result
Java实现
java 复制代码
public class BinaryTreeCameras {
    private int cameras = 0;
    
    public int minCameraCover(TreeNode root) {
        int rootState = dfs(root);
        // 如果根节点未被监控,需要安装摄像头
        if (rootState == 0) {
            cameras++;
        }
        return cameras;
    }
    
    // 0: 未被监控, 1: 有摄像头, 2: 被监控
    private int dfs(TreeNode node) {
        if (node == null) {
            return 2;  // 空节点视为被监控
        }
        
        int left = dfs(node.left);
        int right = dfs(node.right);
        
        // 子节点有未被监控的
        if (left == 0 || right == 0) {
            cameras++;
            return 1;
        }
        
        // 子节点有摄像头
        if (left == 1 || right == 1) {
            return 2;
        }
        
        // 子节点都被监控但无摄像头
        return 0;
    }
}

4.2 二叉树中最大BST子树 (LeetCode 333)

问题描述:找到二叉树中最大的二叉搜索树(BST)子树,返回节点数。

状态定义

返回一个元组:(是否是BST, 最小值, 最大值, 节点数)

Python实现
python 复制代码
def largestBSTSubtree(root):
    """
    二叉树中最大BST子树
    """
    def dfs(node):
        """
        返回: (is_bst, min_val, max_val, node_count)
        """
        if not node:
            return (True, float('inf'), float('-inf'), 0)
        
        left_is_bst, left_min, left_max, left_count = dfs(node.left)
        right_is_bst, right_min, right_max, right_count = dfs(node.right)
        
        # 当前节点为根的树是否是BST
        if (left_is_bst and right_is_bst and 
            left_max < node.val < right_min):
            # 当前节点是BST
            curr_min = min(left_min, node.val)
            curr_max = max(right_max, node.val)
            curr_count = left_count + right_count + 1
            
            # 更新全局最大BST
            nonlocal max_bst_size
            max_bst_size = max(max_bst_size, curr_count)
            
            return (True, curr_min, curr_max, curr_count)
        else:
            # 当前节点不是BST,但为了不影响父节点判断,返回不影响的值
            # 注意:不是BST时,min和max的值不重要
            return (False, float('-inf'), float('inf'), 0)
    
    max_bst_size = 0
    dfs(root)
    return max_bst_size

#### 更健壮的版本
class LargestBST:
    def largestBSTSubtree(self, root: TreeNode) -> int:
        self.max_size = 0
        self._dfs(root)
        return self.max_size
    
    def _dfs(self, node):
        if not node:
            # 空节点是BST,节点数为0
            return (True, float('inf'), float('-inf'), 0)
        
        left_valid, left_min, left_max, left_size = self._dfs(node.left)
        right_valid, right_min, right_max, right_size = self._dfs(node.right)
        
        # 判断以当前节点为根的树是否是BST
        if (left_valid and right_valid and 
            left_max < node.val < right_min):
            # 是BST,计算相关信息
            curr_min = min(left_min, node.val)
            curr_max = max(right_max, node.val)
            curr_size = left_size + right_size + 1
            
            # 更新最大BST大小
            self.max_size = max(self.max_size, curr_size)
            
            return (True, curr_min, curr_max, curr_size)
        else:
            # 不是BST,返回一个不会影响父节点判断的值
            # 任意值都可以,因为父节点会检查is_bst标志
            return (False, 0, 0, 0)
Java实现
java 复制代码
public class LargestBSTSubtree {
    private int maxSize = 0;
    
    public int largestBSTSubtree(TreeNode root) {
        dfs(root);
        return maxSize;
    }
    
    // 返回: {isBST, min, max, size}
    private int[] dfs(TreeNode node) {
        if (node == null) {
            // 空节点是BST,min=MAX, min=MIN, size=0
            return new int[]{1, Integer.MAX_VALUE, Integer.MIN_VALUE, 0};
        }
        
        int[] left = dfs(node.left);
        int[] right = dfs(node.right);
        
        // 如果左右子树都是BST,且满足BST条件
        if (left[0] == 1 && right[0] == 1 && 
            left[2] < node.val && node.val < right[1]) {
            
            int currMin = Math.min(left[1], node.val);
            int currMax = Math.max(right[2], node.val);
            int currSize = left[3] + right[3] + 1;
            
            maxSize = Math.max(maxSize, currSize);
            
            return new int[]{1, currMin, currMax, currSize};
        } else {
            // 不是BST,返回任意值(父节点会检查isBST标志)
            return new int[]{0, 0, 0, 0};
        }
    }
}

5. 多叉树树形DP

5.1 员工的重要性 (LeetCode 690)

问题描述:每个员工有唯一id、重要度和直系下属列表,求某员工及其所有下属的重要度之和。

python 复制代码
class Employee:
    def __init__(self, id: int, importance: int, subordinates: List[int]):
        self.id = id
        self.importance = importance
        self.subordinates = subordinates

def getImportance(employees, id):
    """
    员工的重要性 - 多叉树DFS
    """
    # 构建id到员工的映射
    emp_dict = {emp.id: emp for emp in employees}
    
    def dfs(emp_id):
        employee = emp_dict[emp_id]
        
        # 当前员工的重要度
        total = employee.importance
        
        # 递归计算所有下属的重要度
        for sub_id in employee.subordinates:
            total += dfs(sub_id)
        
        return total
    
    return dfs(id)
Java实现
java 复制代码
public class EmployeeImportance {
    public int getImportance(List<Employee> employees, int id) {
        Map<Integer, Employee> map = new HashMap<>();
        for (Employee emp : employees) {
            map.put(emp.id, emp);
        }
        return dfs(map, id);
    }
    
    private int dfs(Map<Integer, Employee> map, int id) {
        Employee emp = map.get(id);
        int total = emp.importance;
        
        for (int subId : emp.subordinates) {
            total += dfs(map, subId);
        }
        
        return total;
    }
}

5.2 公司派对问题

问题描述:公司要办派对,每个员工有快乐值。如果某个员工参加,则他的直接下属不能参加。求最大快乐值。

python 复制代码
class EmployeeNode:
    def __init__(self, happy=0):
        self.happy = happy
        self.subordinates = []  # 直接下属列表

def maxHappy(root):
    """
    公司派对最大快乐值
    返回: [不选当前员工的最大快乐值, 选当前员工的最大快乐值]
    """
    def dfs(node):
        if not node:
            return [0, 0]
        
        # 当前员工不参加:下属可参加可不参加
        not_join = 0
        # 当前员工参加:下属不能参加
        join = node.happy
        
        for sub in node.subordinates:
            sub_not_join, sub_join = dfs(sub)
            
            # 当前员工不参加,下属可选可不选
            not_join += max(sub_not_join, sub_join)
            
            # 当前员工参加,下属不能参加
            join += sub_not_join
        
        return [not_join, join]
    
    result = dfs(root)
    return max(result[0], result[1])

6. 树形DP优化技巧

6.1 记忆化搜索

对于有重复计算的树形DP,可以使用记忆化搜索。

python 复制代码
def tree_dp_with_memo(root):
    memo = {}
    
    def dfs(node):
        if not node:
            return base_value
        
        if node in memo:
            return memo[node]
        
        left = dfs(node.left)
        right = dfs(node.right)
        
        result = calculate_result(node, left, right)
        memo[node] = result
        
        return result
    
    return dfs(root)

6.2 自底向上迭代

对于某些树形DP,可以使用后序遍历的迭代版本。

python 复制代码
def tree_dp_iterative(root):
    if not root:
        return 0
    
    stack = []
    result_map = {}  # 存储每个节点的计算结果
    
    # 后序遍历
    node = root
    last_visited = None
    
    while stack or node:
        if node:
            stack.append(node)
            node = node.left
        else:
            peek = stack[-1]
            # 如果右子树存在且未被访问
            if peek.right and last_visited != peek.right:
                node = peek.right
            else:
                # 处理当前节点
                node = stack.pop()
                
                # 计算当前节点的结果
                left_result = result_map.get(node.left, base_left)
                right_result = result_map.get(node.right, base_right)
                
                result = calculate_result(node, left_result, right_result)
                result_map[node] = result
                
                last_visited = node
                node = None
    
    return result_map[root]

7. 树形DP解题模板总结

7.1 通用解题步骤

  1. 定义状态

    • 确定每个节点需要返回什么信息
    • 通常返回一个元组或多个值
  2. 确定递归终止条件

    • 空节点返回基础值
    • 叶子节点返回特定值
  3. 递归处理子树

    • 递归处理左右子树
    • 获取子树的结果
  4. 计算当前节点结果

    • 根据子树结果计算当前节点的结果
    • 更新全局变量(如果需要)
  5. 返回结果

    • 返回当前节点的计算结果

7.2 常见状态定义模式

问题类型 返回信息 示例
最大路径和 以节点为起点的最大路径和 (max_gain)
打家劫舍 抢/不抢当前节点的最大收益 (not_rob, rob)
二叉树直径 节点深度 depth
监控二叉树 节点状态(0/1/2) state
最大BST (是否是BST, 最小值, 最大值, 节点数) (is_bst, min, max, size)

7.3 状态转移方程总结

  1. 最大路径和

    max_gain(node) = node.val + max(左max_gain, 右max_gain, 0)
    全局max_sum = max(全局max_sum, node.val + 左max_gain + 右max_gain)

  2. 打家劫舍

    not_rob = max(左not_rob, 左rob) + max(右not_rob, 右rob)
    rob = node.val + 左not_rob + 右not_rob

  3. 二叉树直径

    直径 = max(直径, 左深度 + 右深度)
    深度 = 1 + max(左深度, 右深度)

  4. 监控二叉树

    if 左或右未被监控: return CAMERA
    if 左或右有摄像头: return COVERED
    else: return UNCOVERED

7.4 复杂度分析

问题 时间复杂度 空间复杂度 备注
最大路径和 O(n) O(h) h为树高
打家劫舍III O(n) O(h) 递归栈深度
二叉树直径 O(n) O(h) 后序遍历
监控二叉树 O(n) O(h) 三种状态
最大BST子树 O(n) O(h) 每个节点常数时间

7.5 常见错误与调试

  1. 状态定义不清

    • 混淆局部最优和全局最优
    • 未考虑所有可能的状态
  2. 递归终止条件错误

    • 空节点处理不当
    • 叶子节点特殊处理遗漏
  3. 状态转移错误

    • 遗漏某些转移路径
    • 条件判断不完整
  4. 更新全局变量时机错误

    • 在错误的位置更新全局变量
    • 多次更新导致错误

7.6 调试技巧

  1. 打印递归路径
python 复制代码
def dfs(node, depth=0):
    indent = "  " * depth
    print(f"{indent}Entering node {node.val if node else 'None'}")
    
    # 递归处理...
    
    print(f"{indent}Exiting node {node.val if node else 'None'}")
    return result
  1. 可视化树结构
python 复制代码
def print_tree(node, prefix="", is_left=True):
    if not node:
        return
    
    print_tree(node.right, prefix + ("│   " if is_left else "    "), False)
    print(prefix + ("└── " if is_left else "┌── ") + str(node.val))
    print_tree(node.left, prefix + ("    " if is_left else "│   "), True)
  1. 小规模测试
python 复制代码
# 构建测试树
def build_test_tree():
    # 示例:[-10,9,20,null,null,15,7]
    root = TreeNode(-10)
    root.left = TreeNode(9)
    root.right = TreeNode(20)
    root.right.left = TreeNode(15)
    root.right.right = TreeNode(7)
    return root

8. 进阶练习题目

8.1 基础到中等

  1. 平衡二叉树检查 (LeetCode 110)
  2. 二叉树的所有路径 (LeetCode 257)
  3. 左叶子之和 (LeetCode 404)
  4. 路径总和III (LeetCode 437)

8.2 中等难度

  1. 二叉树的最近公共祖先 (LeetCode 236)
  2. 从叶结点开始的最小字符串 (LeetCode 988)
  3. 在二叉树中分配硬币 (LeetCode 979)
  4. 二叉树中的伪回文路径 (LeetCode 1457)

8.3 进阶挑战

  1. 二叉树的垂序遍历 (LeetCode 987)
  2. 二叉树的序列化与反序列化 (LeetCode 297)
  3. 恢复二叉搜索树 (LeetCode 99)
  4. 二叉树中的链表 (LeetCode 1367)

9. 面试准备建议

9.1 必备技能

  1. 理解树的基本遍历(前序、中序、后序)
  2. 掌握递归和迭代两种实现方式
  3. 熟悉常见树形DP问题的状态定义
  4. 了解时间空间复杂度分析方法

9.2 解题思路

  1. 分析问题:判断是否适合用树形DP
  2. 定义状态:确定每个节点需要什么信息
  3. 设计递归:确定递归函数签名和终止条件
  4. 状态转移:设计状态转移方程
  5. 处理结果:确定如何从递归结果得到最终答案

9.3 沟通技巧

  1. 清晰解释树形DP的思想
  2. 说明状态定义的含义
  3. 解释递归过程和状态转移
  4. 分析时间和空间复杂度
  5. 讨论可能的优化方案

10. 总结

树形DP是动态规划在树结构上的自然延伸,通过递归和分治的思想,将复杂问题分解为子问题。掌握树形DP需要:

核心要点

  1. 理解递归本质:树形DP本质上是递归分治
  2. 合理定义状态:状态要包含解决问题的足够信息
  3. 正确处理边界:空节点和叶子节点的处理
  4. 设计状态转移:如何从子节点状态推导父节点状态
  5. 管理全局信息:如何处理需要全局比较的信息

学习建议

  1. 从简单问题开始,逐步增加难度
  2. 多画图理解递归过程和状态转移
  3. 练习不同种类的树形DP问题
  4. 对比递归和迭代两种实现方式
  5. 总结常见模式,形成解题模板

树形DP不仅是算法面试的重要考点,在实际工程中也有广泛应用(如文件系统、组织结构等)。通过系统学习和大量练习,可以掌握这一重要技能。

相关推荐
亲爱的非洲野猪2 小时前
动态规划进阶:其他经典DP问题深度解析
算法·动态规划
啊阿狸不会拉杆2 小时前
《计算机操作系统》第四章-存储器管理
人工智能·算法·计算机组成原理·os·计算机操作系统
tobias.b2 小时前
408真题解析-2010-11-数据结构-基础排序算法特征
数据结构·算法·排序算法·计算机考研·408真题解析
sali-tec2 小时前
C# 基于OpenCv的视觉工作流-章14-轮廓提取
人工智能·opencv·算法·计算机视觉
东华果汁哥2 小时前
【机器视觉 视频截帧算法】OpenCV 视频截帧算法教程
opencv·算法·音视频
我家大宝最可爱4 小时前
强化学习基础-拒绝采样
人工智能·算法·机器学习
YuTaoShao5 小时前
【LeetCode 每日一题】面试题 17.12. BiNode
算法·leetcode·深度优先
刘大猫.5 小时前
XNMS项目-拓扑图展示
java·人工智能·算法·拓扑·拓扑图·节点树·xnms
夏鹏今天学习了吗7 小时前
【LeetCode热题100(95/100)】寻找重复数
算法·leetcode·职场和发展