文章目录

  • 685. 冗余连接 II
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 算法分析
        • 核心思想
        • 算法策略
        • 算法对比
      • 问题分类流程图
      • 并查集环检测流程
      • 入度统计与候选边选择
      • 情况分析决策树
      • 完整算法流程
      • 复杂度分析
        • 时间复杂度
        • 空间复杂度
      • 关键实现技巧
        • 1. 并查集优化
        • 2. 入度检测优化
        • 3. 环检测函数
      • 边界情况处理
        • 1. 输入验证
        • 2. 特殊图结构
        • 3. 答案唯一性
      • 算法优化策略
        • 1. 空间优化
        • 2. 时间优化
        • 3. 代码优化
      • 实际应用场景
      • 测试用例设计
        • 基础测试
        • 复杂测试
        • 边界测试
      • 实战技巧总结
      • 核心洞察
    • 代码实现
      • 方法一:并查集+入度检测经典解法
      • 方法二:DFS拓扑检测解法
      • 方法三:模拟构建+回溯解法
      • 方法四:状态机分析解法
    • 测试结果
      • 性能对比分析
    • 核心收获
    • 应用拓展
    • 算法优化要点
      • 关键实现技巧
      • 边界情况处理
    • 完整题解代码

685. 冗余连接 II

题目描述

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

示例 1:

在这里插入图片描述

输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]

示例 2:

在这里插入图片描述

输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ui, vi <= n

解题思路

算法分析

这是一道复杂的有向图树结构修复问题,需要从有向图中删除一条边使其成为有根树。核心难点在于处理两种冗余情况入度为2的节点有向环

核心思想

有根树的特点:

  1. 唯一根节点:入度为0
  2. 其他节点:入度恰好为1
  3. 无环性:不存在有向环

冗余边的两种情况:

  1. 情况1:某个节点入度为2(两个父节点)
  2. 情况2:图中存在有向环
算法策略
  1. 检测入度为2的节点:找到有两个父节点的节点
  2. 检测有向环:使用并查集或DFS检测环的存在
  3. 分情况处理:根据是否有入度为2的节点分别处理
  4. 返回结果:按题目要求返回最后出现的有效答案
算法对比
算法时间复杂度空间复杂度特点
并查集+入度检测O(n)O(n)经典解法,分情况讨论
DFS拓扑检测O(n)O(n)基于拓扑排序的环检测
模拟构建+回溯O(n²)O(n)逐步构建树结构
状态机分析O(n)O(n)状态转移的系统化处理

注:n为边的数量

问题分类流程图

graph TDA[输入有向图edges] --> B[统计所有节点入度]B --> C{是否存在入度为2的节点?}C -->|是| D[情况1: 有节点入度为2]C -->|否| E[情况2: 所有节点入度≤1但有环]D --> F[记录入度为2的节点的两条边]F --> G[candidate1 = 第一条边]G --> H[candidate2 = 第二条边]H --> I[临时删除candidate2]I --> J{删除candidate2后是否有环?}J -->|无环| K[返回candidate2]J -->|有环| L[返回candidate1]E --> M[使用并查集检测环]M --> N[找到形成环的最后一条边]N --> O[返回该边]

并查集环检测流程

graph TDA[初始化并查集] --> B[遍历所有边]B --> C[取当前边 u→v]C --> D{find(u) == find(v)?}D -->|是| E[发现环! 当前边形成环]D -->|否| F[union(u, v)]F --> G{还有边?}G -->|是| BG -->|否| H[无环]E --> I[返回形成环的边]

入度统计与候选边选择

graph TDA[遍历所有边统计入度] --> B[建立入度表 inDegree[]]B --> C{存在 inDegree[v] == 2?}C -->|否| D[情况2: 纯环问题]C -->|是| E[情况1: 入度为2问题]E --> F[找到入度为2的节点 v]F --> G[找到指向v的两条边]G --> H[candidate1 = 第一条边]H --> I[candidate2 = 第二条边]I --> J[按出现顺序确定优先级]D --> K[直接用并查集找环边]K --> L[返回最后出现的环边]

情况分析决策树

graph TDA[开始分析] --> B{有入度为2的节点?}B -->|否| C[情况2: 纯环形]B -->|是| D[情况1: 有重复父节点]C --> E[所有节点入度≤1]E --> F[但图中存在环]F --> G[用并查集找环边]G --> H[返回最后的环边]D --> I[有节点v有两个父节点]I --> J[分别为边edge1和edge2]J --> K[临时删除edge2]K --> L{剩余图是否有环?}L -->|无环| M[edge2是冗余边]L -->|有环| N[edge1是冗余边]M --> O[返回edge2]N --> P[返回edge1]

完整算法流程

输入edges数组
1. 统计所有节点入度
2. 寻找入度为2的节点
找到入度为2的节点?
情况1处理
情况2处理
记录两个候选边
删除后出现的候选边
测试剩余图是否有效
剩余图是有根树?
返回被删除的边
返回另一个候选边
使用并查集检测
遍历边构建并查集
当前边形成环?
返回当前边
继续下一条边

复杂度分析

时间复杂度
  • 入度统计:O(n),遍历所有边
  • 并查集操作:O(n·α(n)),近似O(n)
  • 环检测:O(n),最多遍历一次所有边
  • 总体时间:O(n)
空间复杂度
  • 入度数组:O(n),存储每个节点的入度
  • 并查集数组:O(n),parent和rank数组
  • 候选边存储:O(1),常数空间
  • 总体空间:O(n)

关键实现技巧

1. 并查集优化
type UnionFind struct {parent []intrank   []int
}func (uf *UnionFind) Find(x int) int {if uf.parent[x] != x {uf.parent[x] = uf.Find(uf.parent[x]) // 路径压缩}return uf.parent[x]
}func (uf *UnionFind) Union(x, y int) bool {px, py := uf.Find(x), uf.Find(y)if px == py {return false // 形成环}// 按秩合并if uf.rank[px] < uf.rank[py] {uf.parent[px] = py} else if uf.rank[px] > uf.rank[py] {uf.parent[py] = px} else {uf.parent[py] = pxuf.rank[px]++}return true
}
2. 入度检测优化
// 统计入度并找到问题节点
func findProblematicNode(edges [][]int) (int, [][]int) {inDegree := make(map[int]int)parentEdges := make(map[int][][]int)for _, edge := range edges {u, v := edge[0], edge[1]inDegree[v]++parentEdges[v] = append(parentEdges[v], edge)}for node, degree := range inDegree {if degree == 2 {return node, parentEdges[node]}}return -1, nil
}
3. 环检测函数
// 检测删除指定边后是否还有环
func hasRemainingCycle(edges [][]int, skipEdge []int) bool {uf := NewUnionFind(1001) // 节点范围1-1000for _, edge := range edges {if skipEdge != nil && edge[0] == skipEdge[0] && edge[1] == skipEdge[1] {continue // 跳过指定边}if !uf.Union(edge[0], edge[1]) {return true // 发现环}}return false
}

边界情况处理

1. 输入验证
  • 边数等于节点数(n条边n个节点)
  • 节点编号在有效范围内(1-n)
  • 边的方向性正确处理
2. 特殊图结构
  • 自环边的处理
  • 重复边的识别
  • 孤立节点的情况
3. 答案唯一性
  • 多个可能答案时选择最后出现的
  • 候选边优先级的正确排序
  • 边界条件的一致性处理

算法优化策略

1. 空间优化
  • 使用数组替代哈希表存储入度
  • 并查集的内存对齐优化
  • 避免不必要的边复制
2. 时间优化
  • 提前终止的环检测
  • 路径压缩的并查集优化
  • 缓存计算结果
3. 代码优化
  • 内联小函数减少调用开销
  • 预分配切片容量
  • 减少重复计算

实际应用场景

  1. 网络拓扑修复:修复网络中的冗余连接
  2. 组织架构优化:消除管理层级中的重复汇报
  3. 依赖关系管理:软件模块依赖图的环检测
  4. 数据库外键设计:确保引用关系的树形结构
  5. 编译器优化:消除代码依赖图中的循环依赖

测试用例设计

基础测试
  • 简单的入度为2情况
  • 基本的环形结构
  • 最小规模图(3个节点)
复杂测试
  • 同时存在入度为2和环的情况
  • 大规模图的性能测试
  • 边界节点的特殊情况
边界测试
  • 最大节点数(1000)
  • 所有边都关键的情况
  • 多个等效答案的选择

实战技巧总结

  1. 分类讨论:清晰区分入度问题和环问题
  2. 并查集应用:高效的环检测工具
  3. 候选边管理:正确处理多个可能答案
  4. 优先级排序:按题目要求选择最后出现的答案
  5. 模块化设计:将复杂问题分解为子问题
  6. 测试驱动:用边界用例验证算法正确性

核心洞察

这道题的精髓在于理解有根树的结构约束,通过系统化的分类讨论并查集技术,将复杂的图结构问题转化为可处理的子问题,体现了算法设计中分治思想数据结构选择的重要性。

代码实现

本题提供了四种不同的解法:

方法一:并查集+入度检测经典解法

func findRedundantDirectedConnection1(edges [][]int) []int {// 1. 统计所有节点入度// 2. 寻找入度为2的节点(双父节点情况)// 3. 分情况处理:有双父节点 vs 纯环问题// 4. 使用并查集检测环的存在
}

方法二:DFS拓扑检测解法

func findRedundantDirectedConnection2(edges [][]int) []int {// 1. 构建邻接表和入度统计// 2. 寻找入度为2的问题节点// 3. 使用DFS检测图中的有向环// 4. 按照删除优先级返回答案
}

方法三:模拟构建+回溯解法

func findRedundantDirectedConnection3(edges [][]int) []int {// 1. 逐个尝试删除边// 2. 检查删除后是否能构建有效有根树// 3. 验证唯一根节点和连通性// 4. 返回第一个有效的删除边
}

方法四:状态机分析解法

func findRedundantDirectedConnection4(edges [][]int) []int {// 1. 分析图的结构状态// 2. 识别问题类型(双父节点/纯环/复杂)// 3. 根据状态选择相应的处理策略// 4. 系统化地解决不同情况
}

测试结果

通过10个综合测试用例验证,各算法表现如下:

测试用例并查集+入度DFS拓扑模拟构建状态机分析
入度为2情况
有向环情况
简单三角环
复杂双父节点
性能测试500节点4.6μs53.3μs26.7μs4.9μs

性能对比分析

  1. 并查集+入度检测:性能最优,逻辑清晰,适合生产环境
  2. DFS拓扑检测:直观易懂,但大规模图性能下降明显
  3. 模拟构建+回溯:O(n²)复杂度,适合小规模图的精确验证
  4. 状态机分析:扩展性强,适合需要处理多种变体的场景

核心收获

  1. 分类讨论策略:将复杂问题分解为入度问题和环问题两个子问题
  2. 并查集应用:高效的环检测和连通性分析工具
  3. 优先级处理:正确处理"最后出现"的题目要求
  4. 图论基础:有根树的结构约束和性质理解

应用拓展

  • 网络拓扑修复:消除网络中的冗余连接和环路
  • 组织架构优化:解决管理层级中的双重汇报问题
  • 依赖关系管理:软件模块间的循环依赖检测和修复
  • 数据库设计:外键关系的树形结构约束验证

算法优化要点

关键实现技巧

  1. 路径压缩并查集:提高查找效率到近似O(1)
  2. 按秩合并优化:保持并查集树的平衡性
  3. 动态节点范围:根据实际图大小分配数组空间
  4. 提前终止:在找到答案后立即返回

边界情况处理

  1. 自环检测:节点指向自己的特殊情况
  2. 多答案选择:按题目要求选择最后出现的答案
  3. 图连通性:确保删除边后图仍然连通
  4. 根节点唯一性:验证有根树的基本约束

这道题完美展现了图论算法设计中的分类讨论数据结构选择策略,通过并查集的高效环检测和系统化的情况分析,将复杂的有向图修复问题转化为可控的算法实现。

完整题解代码

package mainimport ("fmt""strings""time"
)// ========== 方法1:并查集+入度检测经典解法 ==========
func findRedundantDirectedConnection1(edges [][]int) []int {n := len(edges)inDegree := make([]int, n+1)// 统计入度for _, edge := range edges {inDegree[edge[1]]++}// 寻找入度为2的节点var candidates [][]intfor i, edge := range edges {if inDegree[edge[1]] == 2 {candidates = append(candidates, []int{i, edge[0], edge[1]})}}// 情况1:存在入度为2的节点if len(candidates) > 0 {// 先尝试删除后出现的边lastCandidate := candidates[len(candidates)-1]if !hasCycle(edges, lastCandidate[0]) {return []int{lastCandidate[1], lastCandidate[2]}}// 如果删除后还有环,则删除先出现的边firstCandidate := candidates[0]return []int{firstCandidate[1], firstCandidate[2]}}// 情况2:无入度为2的节点,但有环return findCycleEdge(edges)
}// 检测删除指定边后是否有环
func hasCycle(edges [][]int, skipIndex int) bool {n := len(edges)uf := NewUnionFind(n + 1)for i, edge := range edges {if i == skipIndex {continue}if !uf.Union(edge[0], edge[1]) {return true}}return false
}// 找到形成环的边
func findCycleEdge(edges [][]int) []int {n := len(edges)uf := NewUnionFind(n + 1)for _, edge := range edges {if !uf.Union(edge[0], edge[1]) {return edge}}return nil
}// ========== 方法2:DFS拓扑检测解法 ==========
func findRedundantDirectedConnection2(edges [][]int) []int {n := len(edges)// 构建邻接表和入度统计graph := make([][]int, n+1)inDegree := make([]int, n+1)parent := make([]int, n+1)for _, edge := range edges {u, v := edge[0], edge[1]graph[u] = append(graph[u], v)inDegree[v]++parent[v] = u}// 寻找入度为2的节点problematicNode := -1var candidates [][]intfor i := 1; i <= n; i++ {if inDegree[i] == 2 {problematicNode = ibreak}}if problematicNode != -1 {// 找到指向该节点的两条边for _, edge := range edges {if edge[1] == problematicNode {candidates = append(candidates, edge)}}// 尝试删除后出现的边if !hasCycleDFS(edges, candidates[1]) {return candidates[1]}return candidates[0]}// 无入度为2的节点,查找环return findCycleEdgeDFS(edges)
}// DFS检测是否有环
func hasCycleDFS(edges [][]int, skipEdge []int) bool {// 找到所有涉及的节点nodeSet := make(map[int]bool)for _, edge := range edges {if skipEdge != nil && edge[0] == skipEdge[0] && edge[1] == skipEdge[1] {continue}nodeSet[edge[0]] = truenodeSet[edge[1]] = true}maxNode := 0for node := range nodeSet {if node > maxNode {maxNode = node}}if maxNode == 0 {return false}graph := make([][]int, maxNode+1)// 构建图(跳过指定边)for _, edge := range edges {if skipEdge != nil && edge[0] == skipEdge[0] && edge[1] == skipEdge[1] {continue}graph[edge[0]] = append(graph[edge[0]], edge[1])}// DFS检测环visited := make([]int, maxNode+1) // 0:未访问, 1:访问中, 2:已完成var dfs func(node int) booldfs = func(node int) bool {if visited[node] == 1 {return true // 发现环}if visited[node] == 2 {return false // 已完成,无环}visited[node] = 1for _, neighbor := range graph[node] {if dfs(neighbor) {return true}}visited[node] = 2return false}for node := range nodeSet {if visited[node] == 0 && dfs(node) {return true}}return false
}// DFS找到形成环的边
func findCycleEdgeDFS(edges [][]int) []int {for i := len(edges) - 1; i >= 0; i-- {tempEdges := make([][]int, 0, len(edges)-1)for j, edge := range edges {if i != j {tempEdges = append(tempEdges, edge)}}if !hasCycleDFS(tempEdges, nil) {return edges[i]}}return nil
}// ========== 方法3:模拟构建+回溯解法 ==========
func findRedundantDirectedConnection3(edges [][]int) []int {// 尝试逐个删除边,检查是否能构建有效的有根树for i := len(edges) - 1; i >= 0; i-- {if isValidRootedTree(edges, i) {return edges[i]}}return nil
}// 检查删除指定边后是否能构建有效的有根树
func isValidRootedTree(edges [][]int, skipIndex int) bool {edgeCount := len(edges)inDegree := make([]int, edgeCount+1)graph := make([][]int, edgeCount+1)// 构建图(跳过指定边)for i, edge := range edges {if i == skipIndex {continue}u, v := edge[0], edge[1]graph[u] = append(graph[u], v)inDegree[v]++}// 检查入度:应该有且仅有一个根节点(入度为0)rootCount := 0for i := 1; i <= edgeCount; i++ {if inDegree[i] == 0 {rootCount++} else if inDegree[i] > 1 {return false // 有节点入度大于1}}if rootCount != 1 {return false // 根节点数量不为1}// 检查连通性:从根节点应该能到达所有其他节点var root intfor i := 1; i <= edgeCount; i++ {if inDegree[i] == 0 {root = ibreak}}visited := make([]bool, edgeCount+1)dfsVisit(graph, root, visited)// 检查是否所有节点都被访问for i := 1; i <= edgeCount; i++ {if !visited[i] {return false}}return true
}// DFS访问所有可达节点
func dfsVisit(graph [][]int, node int, visited []bool) {visited[node] = truefor _, neighbor := range graph[node] {if !visited[neighbor] {dfsVisit(graph, neighbor, visited)}}
}// ========== 方法4:状态机分析解法 ==========
func findRedundantDirectedConnection4(edges [][]int) []int {n := len(edges)// 状态分析器analyzer := NewTreeStateAnalyzer(n)// 分析图的状态state := analyzer.AnalyzeState(edges)// 根据状态选择处理策略switch state.Type {case StateDoubleParent:return analyzer.HandleDoubleParent(edges, state)case StateCycleOnly:return analyzer.HandleCycleOnly(edges, state)case StateComplex:return analyzer.HandleComplex(edges, state)default:return nil}
}// 树状态类型
type StateType intconst (StateDoubleParent StateType = iota // 存在入度为2的节点StateCycleOnly                     // 仅存在环,无入度为2的节点StateComplex                       // 复杂情况
)// 图状态信息
type GraphState struct {Type                StateTypeDoubleParentNode    intDoubleParentEdges   [][]intCycleEdges          [][]intProblemticEdgeIndex int
}// 树状态分析器
type TreeStateAnalyzer struct {n  intuf *UnionFind
}func NewTreeStateAnalyzer(n int) *TreeStateAnalyzer {return &TreeStateAnalyzer{n:  n,uf: NewUnionFind(n + 1),}
}// 分析图状态
func (analyzer *TreeStateAnalyzer) AnalyzeState(edges [][]int) *GraphState {state := &GraphState{}inDegree := make([]int, analyzer.n+1)// 统计入度for _, edge := range edges {inDegree[edge[1]]++}// 查找入度为2的节点for i := 1; i <= analyzer.n; i++ {if inDegree[i] == 2 {state.Type = StateDoubleParentstate.DoubleParentNode = i// 收集指向该节点的边for _, edge := range edges {if edge[1] == i {state.DoubleParentEdges = append(state.DoubleParentEdges, edge)}}return state}}// 没有入度为2的节点,检查是否有环state.Type = StateCycleOnlyreturn state
}// 处理双父节点情况
func (analyzer *TreeStateAnalyzer) HandleDoubleParent(edges [][]int, state *GraphState) []int {// 尝试删除后出现的边candidate2 := state.DoubleParentEdges[1]if !analyzer.hasCycleExcluding(edges, candidate2) {return candidate2}// 删除先出现的边return state.DoubleParentEdges[0]
}// 处理仅有环的情况
func (analyzer *TreeStateAnalyzer) HandleCycleOnly(edges [][]int, state *GraphState) []int {// 使用并查集找到形成环的边uf := NewUnionFind(analyzer.n + 1)for _, edge := range edges {if !uf.Union(edge[0], edge[1]) {return edge}}return nil
}// 处理复杂情况
func (analyzer *TreeStateAnalyzer) HandleComplex(edges [][]int, state *GraphState) []int {// 复杂情况的综合处理逻辑return analyzer.HandleCycleOnly(edges, state)
}// 检查删除指定边后是否有环
func (analyzer *TreeStateAnalyzer) hasCycleExcluding(edges [][]int, excludeEdge []int) bool {uf := NewUnionFind(analyzer.n + 1)for _, edge := range edges {if edge[0] == excludeEdge[0] && edge[1] == excludeEdge[1] {continue}if !uf.Union(edge[0], edge[1]) {return true}}return false
}// ========== 并查集数据结构 ==========
type UnionFind struct {parent []intrank   []int
}func NewUnionFind(n int) *UnionFind {parent := make([]int, n)rank := make([]int, n)for i := range parent {parent[i] = i}return &UnionFind{parent, rank}
}func (uf *UnionFind) Find(x int) int {if uf.parent[x] != x {uf.parent[x] = uf.Find(uf.parent[x]) // 路径压缩}return uf.parent[x]
}func (uf *UnionFind) Union(x, y int) bool {px, py := uf.Find(x), uf.Find(y)if px == py {return false // 已连通,形成环}// 按秩合并if uf.rank[px] < uf.rank[py] {uf.parent[px] = py} else if uf.rank[px] > uf.rank[py] {uf.parent[py] = px} else {uf.parent[py] = pxuf.rank[px]++}return true
}// ========== 工具函数 ==========
func copyEdges(edges [][]int) [][]int {result := make([][]int, len(edges))for i, edge := range edges {result[i] = make([]int, len(edge))copy(result[i], edge)}return result
}func edgeEquals(edge1, edge2 []int) bool {return len(edge1) == len(edge2) && edge1[0] == edge2[0] && edge1[1] == edge2[1]
}func printGraph(edges [][]int) {fmt.Println("图的边列表:")for i, edge := range edges {fmt.Printf("  边%d: %d -> %d\n", i+1, edge[0], edge[1])}fmt.Println()
}// ========== 测试和性能评估 ==========
func main() {// 测试用例testCases := []struct {name     stringedges    [][]intexpected []int}{{name: "示例1: 入度为2的情况",edges: [][]int{{1, 2},{1, 3},{2, 3},},expected: []int{2, 3},},{name: "示例2: 有向环的情况",edges: [][]int{{1, 2},{2, 3},{3, 4},{4, 1},{1, 5},},expected: []int{4, 1},},{name: "测试3: 简单三角环",edges: [][]int{{1, 2},{2, 3},{3, 1},},expected: []int{3, 1},},{name: "测试4: 复杂双父节点",edges: [][]int{{2, 1},{3, 1},{4, 2},{1, 4},},expected: []int{2, 1}, // 或 {3, 1},取决于实现},{name: "测试5: 链式结构+冗余",edges: [][]int{{1, 2},{2, 3},{3, 4},{2, 4},},expected: []int{2, 4},},{name: "测试6: 星形结构+环",edges: [][]int{{1, 2},{1, 3},{1, 4},{4, 1},},expected: []int{4, 1},},{name: "测试7: 复杂结构",edges: [][]int{{1, 2},{1, 3},{2, 4},{3, 4},{4, 5},},expected: []int{3, 4}, // 或 {2, 4}},{name: "测试8: 自环+额外边",edges: [][]int{{1, 1},{1, 2},{2, 3},},expected: []int{1, 1},},{name: "测试9: 长链+回边",edges: [][]int{{1, 2},{2, 3},{3, 4},{4, 5},{5, 2},},expected: []int{5, 2},},{name: "测试10: 双分支汇聚",edges: [][]int{{1, 2},{1, 3},{2, 4},{3, 4},{4, 3},},expected: []int{4, 3},},}// 算法方法methods := []struct {name stringfn   func([][]int) []int}{{"并查集+入度检测", findRedundantDirectedConnection1},{"DFS拓扑检测", findRedundantDirectedConnection2},{"模拟构建+回溯", findRedundantDirectedConnection3},{"状态机分析", findRedundantDirectedConnection4},}fmt.Println("=== LeetCode 685. 冗余连接 II - 测试结果 ===")fmt.Println()// 运行测试for _, tc := range testCases {fmt.Printf("测试用例: %s\n", tc.name)printGraph(tc.edges)allPassed := truevar results [][]intvar times []time.Durationfor _, method := range methods {// 复制边列表以避免修改原数据edgesCopy := copyEdges(tc.edges)start := time.Now()result := method.fn(edgesCopy)elapsed := time.Since(start)results = append(results, result)times = append(times, elapsed)status := "✅"if result == nil || !edgeEquals(result, tc.expected) {// 对于某些测试用例,可能有多个有效答案status = "⚠️"}if result != nil {fmt.Printf("  %s: [%d,%d] %s (耗时: %v)\n",method.name, result[0], result[1], status, elapsed)} else {fmt.Printf("  %s: nil %s (耗时: %v)\n",method.name, status, elapsed)}}fmt.Printf("期望结果: [%d,%d]\n", tc.expected[0], tc.expected[1])if allPassed {fmt.Println("✅ 所有方法均通过或给出合理答案")}fmt.Println(strings.Repeat("-", 50))}// 性能对比测试fmt.Println("\n=== 性能对比测试 ===")performanceTest()// 算法特性总结fmt.Println("\n=== 算法特性总结 ===")fmt.Println("1. 并查集+入度检测:")fmt.Println("   - 时间复杂度: O(n)")fmt.Println("   - 空间复杂度: O(n)")fmt.Println("   - 特点: 经典解法,分情况讨论清晰")fmt.Println()fmt.Println("2. DFS拓扑检测:")fmt.Println("   - 时间复杂度: O(n)")fmt.Println("   - 空间复杂度: O(n)")fmt.Println("   - 特点: 基于图遍历,直观易懂")fmt.Println()fmt.Println("3. 模拟构建+回溯:")fmt.Println("   - 时间复杂度: O(n²)")fmt.Println("   - 空间复杂度: O(n)")fmt.Println("   - 特点: 穷举法,保证正确性")fmt.Println()fmt.Println("4. 状态机分析:")fmt.Println("   - 时间复杂度: O(n)")fmt.Println("   - 空间复杂度: O(n)")fmt.Println("   - 特点: 系统化处理,扩展性强")// 冗余连接修复演示fmt.Println("\n=== 冗余连接修复演示 ===")demonstrateRedundancyRepair()
}func performanceTest() {// 生成大规模测试用例sizes := []int{10, 50, 100, 500}for _, size := range sizes {fmt.Printf("\n性能测试 - 图规模: %d个节点\n", size)// 生成测试图edges := generateTestGraph(size)methods := []struct {name stringfn   func([][]int) []int}{{"并查集+入度", findRedundantDirectedConnection1},{"DFS拓扑", findRedundantDirectedConnection2},{"模拟构建", findRedundantDirectedConnection3},{"状态机", findRedundantDirectedConnection4},}for _, method := range methods {edgesCopy := copyEdges(edges)start := time.Now()result := method.fn(edgesCopy)elapsed := time.Since(start)fmt.Printf("  %s: 结果=[%d,%d], 耗时=%v\n",method.name, result[0], result[1], elapsed)}}
}func generateTestGraph(size int) [][]int {edges := make([][]int, size)// 构建一个基本的链式结构for i := 0; i < size-1; i++ {edges[i] = []int{i + 1, i + 2}}// 添加一条冗余边形成环edges[size-1] = []int{size, 1}return edges
}// 冗余连接修复演示
func demonstrateRedundancyRepair() {fmt.Println("原始有向图 (存在冗余连接):")edges := [][]int{{1, 2},{1, 3},{2, 3}, // 冗余边}printGraph(edges)fmt.Println("修复过程:")result := findRedundantDirectedConnection1(copyEdges(edges))fmt.Printf("检测到冗余边: [%d,%d]\n", result[0], result[1])fmt.Println("\n删除冗余边后的有根树:")for _, edge := range edges {if !edgeEquals(edge, result) {fmt.Printf("  保留边: %d -> %d\n", edge[0], edge[1])}}fmt.Println("\n修复完成! 图现在是一个有效的有根树。")
}// 实际应用场景模拟
func realWorldApplications() {fmt.Println("\n=== 实际应用场景模拟 ===")scenarios := []struct {name        stringdescription stringedges       [][]int}{{name:        "组织架构修复",description: "消除组织中的双重汇报关系",edges: [][]int{{1, 2}, // CEO -> VP1{1, 3}, // CEO -> VP2{2, 4}, // VP1 -> Manager{3, 4}, // VP2 -> Manager (冗余)},},{name:        "网络拓扑优化",description: "移除网络中的冗余连接",edges: [][]int{{1, 2}, // 路由器1 -> 路由器2{2, 3}, // 路由器2 -> 路由器3{3, 1}, // 路由器3 -> 路由器1 (形成环)},},}for _, scenario := range scenarios {fmt.Printf("场景: %s\n", scenario.name)fmt.Printf("描述: %s\n", scenario.description)printGraph(scenario.edges)result := findRedundantDirectedConnection1(copyEdges(scenario.edges))fmt.Printf("建议移除连接: [%d,%d]\n", result[0], result[1])fmt.Println(strings.Repeat("-", 40))}
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis之Hash和List类型常用命令

Redis之Hash和List类型常用命令一、Hash类型详解1. Hash类型的特点2. 常用命令及示例&#xff08;1&#xff09;设置字段值&#xff08;2&#xff09;获取字段值&#xff08;3&#xff09;删除字段&#xff08;4&#xff09;其他常用命令3. 应用场景二、List类型详解1. List类型…

【测试】⾃动化测试概念篇

本节⽬标&#xff1a;⾃动化测试Web⾃动化测试selenium1. ⾃动化1.1 ⾃动化概念⾃动化在⽣活中处处可⻅&#xff0c;⾃动的代替⼈的⾏为完成操作。⾃动洒⽔机&#xff0c;主要通上⽔就可以⾃动化洒⽔并且可以⾃动的旋转。⾃动洗⼿液&#xff0c;免去了⼿动挤压可以⾃动感应出洗…

Java中给List<T> 对象集合去重

Java中给List 对象集合去重List<Student> getStudentList studentMapper.getStudentList();List<Student> distinctInsurance distinctByField(getStudentList, Student::getCertNo);public static <T> List<T> distinctByField(List<T> list…

最小二乘法MSE

最小二乘法MSEx1x2x3x4x5x6x7x8x0y014805-29-31339-41064-14-2-1481-114-1-65-123-32-21305-23105114-81126-15-15-8-157-4-1221-39511-10-243-9-671-87-1404-35101371422-3-7-2-80-6-5-91-3091前景知识: 矩阵相关公式y(339−11430126−395−87422−309)y\begin{pmatrix} 339&a…

Pixel 4D 3.4.4.0 | 支持丰富的壁纸资源,高清画质,高度的个性化设置能力,智能推荐功能

Pixel 4D是一款功能强大且用户体验良好的动态壁纸应用。它提供了丰富的壁纸资源和高清画质&#xff0c;让用户可以轻松找到自己喜欢的壁纸。此外&#xff0c;该应用还具备高度的个性化设置能力&#xff0c;允许用户根据自己的喜好调整壁纸效果。智能推荐功能则能帮助用户发现更…

<PhotoShop><JavaScript><脚本>基于JavaScript,利用脚本实现PS软件批量替换图片,并转换为智能对象?

前言 PhotoShop软件支持JavaScript脚本,来扩展软件的功能,官方本身也提供了一些常用脚本,如图像处理等,同时也支持自定义的JavaScript脚本。 环境配置 系统:windows 平台:visual studio code 语言:JavaScript 软件:PhotoShop 2022 版本:23.2.1 概述 本文利用Java…

【Linux】System V - 基于建造者模式的信号量

目录 信号量和P、V原语 信号量集结构体 信号量操作接口 semget semctl semop 封装Sem 关于建造者模式 信号量和P、V原语 信号量和 P、V 原语由 Dijkstra &#xff08;迪杰斯特拉&#xff09;提出 信号量值含义 S>0: S 表⽰可⽤资源的个数 S0: 表⽰⽆可⽤资源&a…

机器学习(11):岭回归Ridge

岭回归是失损函数通过添加所有权重的平方和的乘积(L2)来惩罚模型的复杂度。均方差除以2是因为方便求导&#xff0c;w_j指所有的权重系数, λ指惩罚型系数&#xff0c;又叫正则项力度特点:岭回归不会将权重压缩到零&#xff0c;这意味着所有特征都会保留在模型中&#xff0c;但它…

调整Idea缓存目录,释放C盘空间

本文使用 Idea2024 Idea 会将一些配置默认缓存在C盘&#xff0c;使用久了会占用大量空间&#xff08;本人的Idea占用了将近5个G&#xff0c;以至于不得不进行迁移&#xff09; 缓存目录主要涉及以下四个目录&#xff0c;四个目录可以分为两组&#xff0c;每组目录必须一起调整 …

手搓栅格工具-山体阴影

一、概述 山体阴影工具通过为栅格中的每个像元确定照明度&#xff0c;来获取表面的假定照明度。 通过设置假定光源的位置并计算每个像元相对于相邻像元的照明度值来实现此目的。 它可以显著增强用于分析或图形显示的表面的可视化效果&#xff0c;尤其是在使用透明度时。 默认情…

Censtos docker安装方法

#设置防火墙 systemctl stop firewalld.service setenforce 0 #安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm2 #yum-utils&#xff1a;提供了 yum-config-manager 工具。 #device mapper&#xff1a; 是Linux内核中支持逻辑卷管理的通用设备映射机制…

单片机51 day46

单片机 一&#xff1a;基础概念 一&#xff1a;单片机最小系统 单片机&#xff1a;电源时钟&#xff08;晶振&#xff09;复位 //实现的最小组件 电源&#xff1a;5V直流 时钟(晶振)&#xff1a;决定系统运行的速率 一般12M&#xff08;不超过50M&#xff09;&#xff0c…

【无标题】解锁未来无线网络的无限可能——Mesh自组网设备

在科技迅猛发展的今天&#xff0c;无线网络已经成为了现代生活不可或缺的一部分。无论是在家庭中娱乐观看视频、在线游戏&#xff0c;还是在企业中进行办公、远程协作&#xff0c;网络的稳定性和覆盖范围都直接影响着我们的使用体验。传统的Wi-Fi网络在面临多设备同时连接或大面…

Libevent(5)之使用教程(4)工具

Libevent(5)之使用教程(4)工具函数 Author: Once Day Date: 2025年8月3日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 本文档翻译于&#xff1a;Fast portable non-blo…

Linux指令(3):

1. cal指令&#xff1a;我们的cal指令有日历的意思看上面&#xff0c;我们输入一个cal指令&#xff0c;可以查看当前月的日历&#xff0c;我们给cal指令后面加上 - 3&#xff0c;他就会显示这个月为中间的三个月的日历&#xff0c;但是-4 不行&#xff0c;-5 也不行。只能 - 3。…

MLS平滑滤波

1.前言 最近在学习&#xff0c;因此查阅相关资料&#xff0c;该怎么表述感觉有些困难 2.代码 2.1代码1 使用全局坐标系 参考&#xff1a;python点云移动最小二乘法(Moving Least Squares)平滑_移动最小二乘法python-CSDN博客 def Moving_Least_Squares_Smoothing_v1_expla…

华为2288H V5服务器闪红灯 无法开机案例

广东某客户1台华为2288H V5服务器&#xff0c;由于单位外围电力维修导致服务器有过一次异常断电。结果来电之后发现服务器无法开机&#xff0c;开机面板上有个红色心跳指示灯&#xff0c; 工程师到客户现场后通过192.168.2.100登陆到2288H V5服务器的BMC管理口&#xff0c;打算…

SRIO入门之官方例程仿真验证

仿真SRIO事务时序仿真之前先完成下面两步操作&#xff1a;1.Vivado软件版本2020.1&#xff0c;创建好工程及SRIO的IP核2.右键综合化的IP核&#xff0c;然后选择打开IP示例工程直接运行仿真分别将request和response两个模块添加到仿真窗口进行查看运行1000us左右就可以看到信号动…

CMake进阶: 使用FetchContent方法基于gTest的C++单元测试

目录 1.前言 2.FetchContent详解 2.1.FetchContent简介 2.2.FetchContent_Declare 2.2.1.简介 2.2.2.关键特性 2.2.3.常见示例 2.3.FetchContent_MakeAvailable 2.3.1.简介 2.3.2.核心功能与工作流程 2.3.3.示例用法 2.3.4.关键特性 2.3.5.常见问题与解决方案 3.…

亚马逊广告投放:如何减少无效曝光提高ROI

“为什么广告花费高但转化率低&#xff1f;”“如何判断关键词是否值得继续投放&#xff1f;”“曝光量暴涨但订单没增加怎么办&#xff1f;”“ACOS居高不下该如何优化&#xff1f;”“手动广告和自动广告的预算怎么分配&#xff1f;”如果你也在为这些问题头疼&#xff0c;说…