当前位置: 首页 > news >正文

【华为机试】113. 路径总和 II

文章目录

  • 113. 路径总和 II
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 示例 3:
    • 提示:
    • 解题思路
      • 算法分析
        • 核心思想
        • 关键要素
        • 算法对比
      • DFS递归回溯流程
      • 回溯机制详解
      • 完整搜索过程
      • BFS层序遍历方法
      • 复杂度分析
        • 时间复杂度
        • 空间复杂度
      • 关键实现技巧
        • 1. 路径状态管理
        • 2. 引用传递+手动回溯
        • 3. BFS迭代实现
      • 边界情况处理
        • 1. 空树处理
        • 2. 负数处理
        • 3. 路径复制
      • 算法优化策略
        • 1. 空间优化
        • 2. 时间优化
        • 3. 内存优化
      • 实际应用场景
      • 测试用例设计
        • 基础测试
        • 边界测试
        • 性能测试
      • 实战技巧总结
      • 核心洞察
    • 完整题解代码

113. 路径总和 II

题目描述

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

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

示例 1:

在这里插入图片描述

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

在这里插入图片描述

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

提示:

  • 树中节点总数在范围 [0, 5000] 内
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

解题思路

算法分析

这是一道经典的二叉树路径搜索问题,需要找到所有从根节点到叶子节点且路径和等于目标值的路径。核心思想是深度优先搜索(DFS) + 回溯算法

核心思想
  1. DFS遍历:从根节点开始,深度优先遍历所有可能路径
  2. 路径记录:在遍历过程中记录当前路径上的所有节点
  3. 条件判断:到达叶子节点时检查路径和是否等于目标值
  4. 回溯处理:遍历完一个分支后,回溯到上一层继续探索
关键要素
  • 叶子节点判断node.Left == nil && node.Right == nil
  • 路径和计算:累计当前路径上所有节点的值
  • 结果收集:满足条件的路径加入结果集
  • 回溯机制:确保路径状态的正确维护
算法对比
算法时间复杂度空间复杂度特点
DFS递归回溯O(N)O(H)经典解法,代码简洁
DFS迭代栈O(N)O(H)显式栈,空间可控
BFS层序遍历O(N)O(W)层次遍历,适合宽树
Morris遍历O(N)O(1)空间最优,实现复杂

注:N为节点数,H为树高,W为树的最大宽度

DFS递归回溯流程

graph TDA[开始DFS: root, targetSum] --> B{当前节点为空?}B -->|是| C[返回,路径无效]B -->|否| D[将当前节点值加入路径]D --> E[更新剩余目标和: targetSum - node.val]E --> F{是否为叶子节点?}F -->|是| G{剩余目标和 == 0?}F -->|否| H[递归遍历左子树]G -->|是| I[将当前路径加入结果集]G -->|否| J[路径不符合,忽略]H --> K[递归遍历右子树]I --> L[回溯: 移除当前节点]J --> LK --> LL --> M[返回上一层]

回溯机制详解

graph TDA[进入节点] --> B[path.add(node.val)]B --> C[递归处理子节点]C --> D{处理完所有子节点?}D -->|否| E[继续处理下一个子节点]D -->|是| F[path.remove(最后一个元素)]F --> G[返回上一层]E --> C

完整搜索过程

graph TDA[root=5, target=22, path=[]] --> B[path=[5], remaining=17]B --> C[左子树: 4]B --> D[右子树: 8]C --> E[path=[5,4], remaining=13]E --> F[左子树: 11]F --> G[path=[5,4,11], remaining=2]G --> H[左子树: 7]G --> I[右子树: 2]H --> J[path=[5,4,11,7], remaining=-5]J --> K[叶子节点,和不匹配]I --> L[path=[5,4,11,2], remaining=0]L --> M[叶子节点,和匹配! 收集路径]D --> N[path=[5,8], remaining=14]N --> O[左子树: 13]N --> P[右子树: 4]P --> Q[path=[5,8,4], remaining=10]Q --> R[左子树: 5]Q --> S[右子树: 1]R --> T[path=[5,8,4,5], remaining=5]T --> U[叶子节点,但和不为0]subgraph 结果收集V[收集到的路径:] W[[5,4,11,2]]X[[5,8,4,5]]end

BFS层序遍历方法

graph TDA[初始化队列: node, path, sum] --> B[队列非空?]B -->|否| C[遍历结束]B -->|是| D[出队: 当前节点、路径、累计和]D --> E{是否为叶子节点?}E -->|是| F{累计和 == 目标和?}E -->|否| G[处理左右子节点]F -->|是| H[添加路径到结果]F -->|否| I[忽略此路径]G --> J[左子节点入队: newPath+left, sum+left.val]G --> K[右子节点入队: newPath+right, sum+right.val]H --> L[继续处理下一个节点]I --> LJ --> LK --> LL --> B

复杂度分析

时间复杂度
  • 访问节点:O(N),每个节点访问一次
  • 路径复制:O(H×K),H为树高,K为有效路径数
  • 总体时间:O(N + H×K),通常简化为O(N)
空间复杂度
  • 递归栈:O(H),最大递归深度
  • 路径存储:O(H),当前路径最大长度
  • 结果存储:O(H×K),K个路径,每个最长H
  • 总体空间:O(H + H×K) = O(H×K)

关键实现技巧

1. 路径状态管理
// 方法1: 值传递(自动回溯)
func dfs(node *TreeNode, targetSum int, path []int, result *[][]int) {if node == nil {return}// 添加当前节点到路径(新切片)newPath := append(path, node.Val)if node.Left == nil && node.Right == nil && targetSum == node.Val {*result = append(*result, newPath)return}// 递归时传递新路径,无需手动回溯dfs(node.Left, targetSum-node.Val, newPath, result)dfs(node.Right, targetSum-node.Val, newPath, result)
}
2. 引用传递+手动回溯
// 方法2: 引用传递(手动回溯)
func dfs(node *TreeNode, targetSum int, path *[]int, result *[][]int) {if node == nil {return}// 添加当前节点*path = append(*path, node.Val)if node.Left == nil && node.Right == nil && targetSum == node.Val {// 复制路径到结果pathCopy := make([]int, len(*path))copy(pathCopy, *path)*result = append(*result, pathCopy)} else {// 递归处理子节点dfs(node.Left, targetSum-node.Val, path, result)dfs(node.Right, targetSum-node.Val, path, result)}// 回溯:移除当前节点*path = (*path)[:len(*path)-1]
}
3. BFS迭代实现
type PathNode struct {node *TreeNodepath []intsum  int
}func pathSumBFS(root *TreeNode, targetSum int) [][]int {if root == nil {return [][]int{}}var result [][]intqueue := []*PathNode{{root, []int{root.Val}, root.Val}}for len(queue) > 0 {current := queue[0]queue = queue[1:]if current.node.Left == nil && current.node.Right == nil {if current.sum == targetSum {result = append(result, current.path)}continue}if current.node.Left != nil {leftPath := append([]int{}, current.path...)leftPath = append(leftPath, current.node.Left.Val)queue = append(queue, &PathNode{current.node.Left, leftPath, current.sum + current.node.Left.Val,})}if current.node.Right != nil {rightPath := append([]int{}, current.path...)rightPath = append(rightPath, current.node.Right.Val)queue = append(queue, &PathNode{current.node.Right, rightPath, current.sum + current.node.Right.Val,})}}return result
}

边界情况处理

1. 空树处理
  • 根节点为nil:返回空结果
  • 单节点树:检查节点值是否等于目标和
2. 负数处理
  • 节点值可能为负数
  • 目标和可能为负数
  • 路径和的计算需要考虑正负抵消
3. 路径复制
  • 确保结果中的路径是独立的副本
  • 避免引用同一个切片导致的数据污染

算法优化策略

1. 空间优化
  • 使用值传递避免手动回溯
  • 预估结果集大小,减少切片扩容
  • 路径长度预分配
2. 时间优化
  • 提前剪枝:如果当前节点值已经超过剩余目标和(所有后续节点都为正数的情况)
  • 路径长度限制:如果路径长度超过合理范围
3. 内存优化
  • 复用临时切片
  • 及时释放不再使用的路径

实际应用场景

  1. 决策树分析:业务流程中的路径决策
  2. 游戏路径规划:RPG游戏中的任务路径
  3. 文件系统遍历:查找特定属性的文件路径
  4. 网络路由:寻找满足条件的网络路径
  5. 基因序列分析:DNA序列中的特定模式匹配

测试用例设计

基础测试
  • 单节点树(正数、负数、零)
  • 简单二叉树(有解、无解)
  • 多路径树(多个有效路径)
边界测试
  • 空树
  • 目标和为0
  • 所有节点值为负数
  • 路径和溢出边界
性能测试
  • 深度优先的极端情况(链式树)
  • 广度优先的极端情况(满二叉树)
  • 大规模随机树

实战技巧总结

  1. 路径管理:选择合适的路径维护策略(值传递vs引用传递)
  2. 回溯思想:理解回溯的本质是状态恢复
  3. 边界判断:准确识别叶子节点和空节点
  4. 结果收集:确保路径副本的正确性
  5. 性能权衡:在代码简洁性和性能之间找到平衡
  6. 测试覆盖:考虑各种边界情况和特殊输入

核心洞察

这道题的核心在于理解树的遍历和路径状态管理,通过DFS+回溯的经典模式,系统地探索所有可能路径,体现了递归分解状态恢复的重要思想,是掌握树形结构算法的关键题目。

完整题解代码

package mainimport ("fmt""reflect""strings""time"
)// TreeNode 二叉树节点定义
type TreeNode struct {Val   intLeft  *TreeNodeRight *TreeNode
}// ========== 方法1: DFS递归回溯(值传递) ==========
func pathSum1(root *TreeNode, targetSum int) [][]int {var result [][]intvar path []intdfs1(root, targetSum, path, &result)return result
}func dfs1(node *TreeNode, remain int, path []int, result *[][]int) {if node == nil {return}// 添加当前节点到路径path = append(path, node.Val)remain -= node.Val// 如果是叶节点且路径和等于目标和if node.Left == nil && node.Right == nil && remain == 0 {// 复制路径,避免引用问题pathCopy := make([]int, len(path))copy(pathCopy, path)*result = append(*result, pathCopy)return}// 递归遍历左右子树dfs1(node.Left, remain, path, result)dfs1(node.Right, remain, path, result)
}// ========== 方法2: DFS递归回溯(引用传递,手动回溯) ==========
func pathSum2(root *TreeNode, targetSum int) [][]int {var result [][]intvar path []intdfs2(root, targetSum, &path, &result)return result
}func dfs2(node *TreeNode, remain int, path *[]int, result *[][]int) {if node == nil {return}// 添加当前节点到路径*path = append(*path, node.Val)remain -= node.Val// 如果是叶节点且路径和等于目标和if node.Left == nil && node.Right == nil && remain == 0 {pathCopy := make([]int, len(*path))copy(pathCopy, *path)*result = append(*result, pathCopy)} else {// 继续搜索子树dfs2(node.Left, remain, path, result)dfs2(node.Right, remain, path, result)}// 回溯:移除当前节点*path = (*path)[:len(*path)-1]
}// ========== 方法3: BFS层序遍历 ==========
func pathSum3(root *TreeNode, targetSum int) [][]int {if root == nil {return [][]int{}}var result [][]inttype pathInfo struct {node *TreeNodepath []intsum  int}queue := []pathInfo{{root, []int{root.Val}, root.Val}}for len(queue) > 0 {current := queue[0]queue = queue[1:]node := current.nodepath := current.pathsum := current.sum// 如果是叶节点if node.Left == nil && node.Right == nil {if sum == targetSum {result = append(result, path)}continue}// 处理左子树if node.Left != nil {leftPath := make([]int, len(path))copy(leftPath, path)leftPath = append(leftPath, node.Left.Val)queue = append(queue, pathInfo{node: node.Left,path: leftPath,sum:  sum + node.Left.Val,})}// 处理右子树if node.Right != nil {rightPath := make([]int, len(path))copy(rightPath, path)rightPath = append(rightPath, node.Right.Val)queue = append(queue, pathInfo{node: node.Right,path: rightPath,sum:  sum + node.Right.Val,})}}return result
}// ========== 方法4: DFS迭代栈实现 ==========
func pathSum4(root *TreeNode, targetSum int) [][]int {if root == nil {return [][]int{}}var result [][]inttype stackInfo struct {node *TreeNodepath []intsum  int}stack := []stackInfo{{root, []int{root.Val}, root.Val}}for len(stack) > 0 {current := stack[len(stack)-1]stack = stack[:len(stack)-1]node := current.nodepath := current.pathsum := current.sum// 如果是叶节点if node.Left == nil && node.Right == nil {if sum == targetSum {result = append(result, path)}continue}// 右子树先入栈(后处理)if node.Right != nil {rightPath := make([]int, len(path))copy(rightPath, path)rightPath = append(rightPath, node.Right.Val)stack = append(stack, stackInfo{node: node.Right,path: rightPath,sum:  sum + node.Right.Val,})}// 左子树后入栈(先处理)if node.Left != nil {leftPath := make([]int, len(path))copy(leftPath, path)leftPath = append(leftPath, node.Left.Val)stack = append(stack, stackInfo{node: node.Left,path: leftPath,sum:  sum + node.Left.Val,})}}return result
}// ========== 方法5: 优化DFS(预分配优化) ==========
func pathSum5(root *TreeNode, targetSum int) [][]int {var result [][]intpath := make([]int, 0, 1000) // 预分配容量dfs5(root, targetSum, path, &result)return result
}func dfs5(node *TreeNode, remain int, path []int, result *[][]int) {if node == nil {return}// 添加当前节点path = append(path, node.Val)remain -= node.Val// 检查叶节点if node.Left == nil && node.Right == nil && remain == 0 {pathCopy := make([]int, len(path))copy(pathCopy, path)*result = append(*result, pathCopy)return}// 继续搜索dfs5(node.Left, remain, path, result)dfs5(node.Right, remain, path, result)
}// ========== 工具函数 ==========// 根据数组构建二叉树
func buildTree(values []interface{}) *TreeNode {if len(values) == 0 {return nil}root := &TreeNode{Val: values[0].(int)}queue := []*TreeNode{root}index := 1for len(queue) > 0 && index < len(values) {node := queue[0]queue = queue[1:]// 左子节点if index < len(values) && values[index] != nil {node.Left = &TreeNode{Val: values[index].(int)}queue = append(queue, node.Left)}index++// 右子节点if index < len(values) && values[index] != nil {node.Right = &TreeNode{Val: values[index].(int)}queue = append(queue, node.Right)}index++}return root
}// 层序打印树结构
func printTree(root *TreeNode) {if root == nil {fmt.Println("Empty tree")return}queue := []*TreeNode{root}level := 0for len(queue) > 0 {size := len(queue)fmt.Printf("Level %d: ", level)allNull := truefor i := 0; i < size; i++ {node := queue[0]queue = queue[1:]if node != nil {fmt.Printf("%d ", node.Val)queue = append(queue, node.Left)queue = append(queue, node.Right)allNull = false} else {fmt.Print("null ")queue = append(queue, nil, nil)}}fmt.Println()level++// 如果下一层全是null,停止打印if allNull {break}}
}// 打印路径列表
func printPaths(paths [][]int) {if len(paths) == 0 {fmt.Println("[]")return}fmt.Print("[")for i, path := range paths {if i > 0 {fmt.Print(",")}fmt.Print("[")for j, val := range path {if j > 0 {fmt.Print(",")}fmt.Print(val)}fmt.Print("]")}fmt.Println("]")
}// 比较两个路径列表是否相等
func equalPaths(paths1, paths2 [][]int) bool {if len(paths1) != len(paths2) {return false}// 转换为字符串集合进行比较(顺序无关)set1 := make(map[string]bool)set2 := make(map[string]bool)for _, path := range paths1 {set1[fmt.Sprintf("%v", path)] = true}for _, path := range paths2 {set2[fmt.Sprintf("%v", path)] = true}return reflect.DeepEqual(set1, set2)
}// ========== 测试和性能评估 ==========
func main() {// 测试用例testCases := []struct {name      stringtreeVals  []interface{}targetSum intexpected  [][]int}{{name:      "示例1: 复杂二叉树",treeVals:  []interface{}{5, 4, 8, 11, nil, 13, 4, 7, 2, nil, nil, nil, nil, 5, 1},targetSum: 22,expected:  [][]int{{5, 4, 11, 2}},},{name:      "示例2: 无解情况",treeVals:  []interface{}{1, 2, 3},targetSum: 5,expected:  [][]int{},},{name:      "示例3: 目标和为0",treeVals:  []interface{}{1, 2},targetSum: 0,expected:  [][]int{},},{name:      "测试4: 单节点树",treeVals:  []interface{}{5},targetSum: 5,expected:  [][]int{{5}},},{name:      "测试5: 单节点不匹配",treeVals:  []interface{}{5},targetSum: 3,expected:  [][]int{},},{name:      "测试6: 负数节点",treeVals:  []interface{}{1, -2, -3, 1, 3, -2, nil, -1},targetSum: 2,expected:  [][]int{{1, -2, 3}},},{name:      "测试7: 全负数",treeVals:  []interface{}{-1, -2, -3},targetSum: -3,expected:  [][]int{{-1, -2}},},{name:      "测试8: 链式结构",treeVals:  []interface{}{1, 2, nil, 3, nil, 4, nil},targetSum: 10,expected:  [][]int{{1, 2, 3, 4}},},{name:      "测试9: 多路径相同和",treeVals:  []interface{}{10, 5, 15, 3, 7, nil, 18},targetSum: 18,expected:  [][]int{{10, 5, 3}},},{name:      "测试10: 空树",treeVals:  []interface{}{},targetSum: 0,expected:  [][]int{},},}// 算法方法methods := []struct {name stringfn   func(*TreeNode, int) [][]int}{{"DFS递归回溯(值传递)", pathSum1},{"DFS递归回溯(引用传递)", pathSum2},{"BFS层序遍历", pathSum3},{"DFS迭代栈", pathSum4},{"优化DFS", pathSum5},}fmt.Println("=== LeetCode 113. 路径总和 II - 测试结果 ===")fmt.Println()// 运行测试for _, tc := range testCases {fmt.Printf("测试用例: %s\n", tc.name)fmt.Printf("目标和: %d\n", tc.targetSum)// 构建测试树root := buildTree(tc.treeVals)fmt.Println("二叉树结构:")printTree(root)allPassed := truevar results [][][]intvar times []time.Durationfor _, method := range methods {start := time.Now()result := method.fn(root, tc.targetSum)elapsed := time.Since(start)results = append(results, result)times = append(times, elapsed)status := "✅"if !equalPaths(result, tc.expected) {status = "❌"allPassed = false}fmt.Printf("  %s: %s (耗时: %v)\n", method.name, status, elapsed)fmt.Print("    结果: ")printPaths(result)}fmt.Print("期望结果: ")printPaths(tc.expected)if allPassed {fmt.Println("✅ 所有方法均通过")} else {fmt.Println("❌ 存在失败的方法")}fmt.Println(strings.Repeat("-", 60))}// 性能对比测试fmt.Println("\n=== 性能对比测试 ===")performanceTest()// 算法特性总结fmt.Println("\n=== 算法特性总结 ===")fmt.Println("1. DFS递归回溯(值传递):")fmt.Println("   - 时间复杂度: O(N)")fmt.Println("   - 空间复杂度: O(H)")fmt.Println("   - 特点: 代码简洁,自动回溯")fmt.Println()fmt.Println("2. DFS递归回溯(引用传递):")fmt.Println("   - 时间复杂度: O(N)")fmt.Println("   - 空间复杂度: O(H)")fmt.Println("   - 特点: 空间效率高,需手动回溯")fmt.Println()fmt.Println("3. BFS层序遍历:")fmt.Println("   - 时间复杂度: O(N)")fmt.Println("   - 空间复杂度: O(W)")fmt.Println("   - 特点: 层次处理,适合宽树")fmt.Println()fmt.Println("4. DFS迭代栈:")fmt.Println("   - 时间复杂度: O(N)")fmt.Println("   - 空间复杂度: O(H)")fmt.Println("   - 特点: 显式栈,避免递归")fmt.Println()fmt.Println("5. 优化DFS:")fmt.Println("   - 时间复杂度: O(N)")fmt.Println("   - 空间复杂度: O(H)")fmt.Println("   - 特点: 预分配优化,减少内存分配")// 路径搜索演示fmt.Println("\n=== 路径搜索演示 ===")demoPathSearch()
}// 性能测试
func performanceTest() {sizes := []int{100, 500, 1000, 2000}methods := []struct {name stringfn   func(*TreeNode, int) [][]int}{{"DFS值传递", pathSum1},{"DFS引用传递", pathSum2},{"BFS层序", pathSum3},{"DFS迭代", pathSum4},{"优化DFS", pathSum5},}for _, size := range sizes {fmt.Printf("性能测试 - 树大小: %d个节点\n", size)// 构建测试树root := buildBalancedTree(size)targetSum := size + 100 // 不太可能匹配的目标和for _, method := range methods {start := time.Now()result := method.fn(root, targetSum)elapsed := time.Since(start)fmt.Printf("  %s: 找到%d条路径, 耗时=%v\n",method.name, len(result), elapsed)}}
}// 构建平衡测试树
func buildBalancedTree(size int) *TreeNode {if size <= 0 {return nil}values := make([]interface{}, size)for i := 0; i < size; i++ {values[i] = i + 1}return buildTree(values)
}// 路径搜索演示
func demoPathSearch() {fmt.Println("构建示例树:")treeVals := []interface{}{5, 4, 8, 11, nil, 13, 4, 7, 2, nil, nil, nil, nil, 5, 1}root := buildTree(treeVals)printTree(root)fmt.Println("寻找路径和为 22 的所有路径:")result := pathSum1(root, 22)fmt.Printf("找到 %d 条有效路径:\n", len(result))for i, path := range result {sum := 0for _, val := range path {sum += val}fmt.Printf("路径 %d: %v (和=%d)\n", i+1, path, sum)}fmt.Println("路径搜索完成!")
}
http://www.dtcms.com/a/320302.html

相关文章:

  • Java异常处理机制深度解析:构建健壮程序的核心
  • C++ AVL树实现详解:理论+代码+图解
  • 使用Cloud Document Converter将飞书文档导出为markdown
  • 神经网络中一般都包含哪些关键层,每一层的作用是什么?
  • Gemini-CLI-项目原理流程总结
  • 大模型2位量化原理解析
  • Redis面试精讲 Day 16:Redis性能监控与分析工具
  • Microsoft Office PowerPoint 制作简单的游戏素材
  • 腾讯位置服务 —— 预估订单路线金额(使用Drools规则引擎处理)
  • Gitee上免费搭建博客
  • 基于C++深度学习 (NCNN、MNN、OpenVINO)OpenCV 等实践
  • 第二集 测试概念
  • 8月7号打卡
  • python---函数的形参与实参
  • C++的入门学习
  • 拷贝数组练习
  • 瞬态吸收光谱仪的基本原理
  • Ubuntu 系统 Docker 启动失败(iptables/nf\_tables)
  • 【CodeButty + 自制MCP】给AI装上翅膀,快速绘制思维导图
  • 驱动-设备树插件注册子系统
  • 【机器学习深度学习】大模型应用落地:微调与RAG的角色与实践
  • 为什么需要日志收集系统
  • 人工智能——自动微分
  • 大数据中需要知道的监控页面端口号都有哪些
  • C语言学习笔记——文件
  • 基于Python的实习僧招聘数据采集与可视化分析,使用matplotlib进行可视化
  • iptables封堵实验
  • Java——详解形参实参方法的重载
  • 《C语言》函数练习题--3
  • (易视宝)易视TV is-E4-G-全志A20芯片-安卓4-烧写卡刷工具及教程