求构成权重和为L的简单路径所需的最少边数
带权树中寻找权重和为L的最少边数简单路径的高效解法
问题定义
给定带权树T,需找到权重和恰好为L的简单路径(无重复顶点/边),返回该路径所需的最少边数;若不存在这样的路径,返回-1。补充说明:每个测试用例的L可能不同,树包含N个顶点,编号0到N-1。
现有解法的瓶颈
你当前的暴力解法通过遍历所有N²条路径来寻找答案,对应的Java代码可正常运行,但当N达到10000时会因时间复杂度O(N²)超时。尝试过二分答案,但验证某一边数是否可行的难度与原问题相当,无法有效优化。
高效解法思路
树中的任意简单路径只有两种形式:
- 从某个节点出发向下延伸的单链路径;
- 经过某个公共祖先的两条单链路径组合(路径两端在该祖先的不同子树中)。
我们可以通过DFS遍历+哈希表维护前缀和的方式,将时间复杂度优化到O(N log N),具体步骤如下:
- 遍历树时,记录从根到当前节点的路径权重和
currentSum,以及对应的边数currentCnt; - 用哈希表存储已访问路径的前缀和,以及该前缀和对应的最小边数(同一前缀和对应多个边数时,保留最小的才能得到最优解);
- 对每个节点,检查是否存在前缀和
needed = currentSum - L:若存在,则路径从对应节点到当前节点的权重和为L,边数为currentCnt - sumMap.get(needed),更新全局最小边数; - 回溯时移除当前节点的前缀和记录,避免统计同一子树内的重复路径。
若树中所有边权重均为正,还可以用排序前缀和+双指针的方式进一步优化,但哈希表的方法更通用(支持负权重)。
优化后的Java代码
import java.util.*; class Edge { int toVertex, weight; Edge(int v, int w) { toVertex = v; weight = w; } } class Solver { private static long targetL; private static int minEdges; private static Map<Long, Integer> sumToMinCnt; public static int solve(List<List<Edge>> tree, long L) { targetL = L; minEdges = Integer.MAX_VALUE; sumToMinCnt = new HashMap<>(); // 初始状态:虚拟根的前缀和为0,边数为0 sumToMinCnt.put(0L, 0); dfs(tree, 0, -1, 0L, 0); return minEdges == Integer.MAX_VALUE ? -1 : minEdges; } private static void dfs(List<List<Edge>> tree, int currNode, int parent, long currSum, int currEdgeCnt) { // 检查是否存在前缀和使得currSum - sum = targetL long requiredSum = currSum - targetL; if (sumToMinCnt.containsKey(requiredSum)) { minEdges = Math.min(minEdges, currEdgeCnt - sumToMinCnt.get(requiredSum)); } // 记录当前前缀和的最小边数:如果已有相同sum,保留更小的边数 sumToMinCnt.put(currSum, Math.min( sumToMinCnt.getOrDefault(currSum, Integer.MAX_VALUE), currEdgeCnt )); // 遍历子节点 for (Edge edge : tree.get(currNode)) { int nextNode = edge.toVertex; if (nextNode == parent) continue; dfs(tree, nextNode, currNode, currSum + edge.weight, currEdgeCnt + 1); } // 回溯:如果当前前缀和的边数是我们记录的最小值,就移除该条目,避免影响其他子树 if (sumToMinCnt.get(currSum) == currEdgeCnt) { sumToMinCnt.remove(currSum); } } } public class Main { static void putEdge(List<List<Edge>> tree, int v1, int v2, int weight) { tree.get(v1).add(new Edge(v2, weight)); tree.get(v2).add(new Edge(v1, weight)); } public static void main(String[] args) { List<List<Edge>> tree = new ArrayList<>(); int N = 8; for (int i = 0; i < N; i++) tree.add(new ArrayList<>()); putEdge(tree, 0, 1, 2); putEdge(tree, 1, 2, 1); putEdge(tree, 1, 3, 2); putEdge(tree, 2, 6, 1); putEdge(tree, 6, 7, 1); putEdge(tree, 3, 4, 1); putEdge(tree, 3, 5, 4); System.out.println(Solver.solve(tree, 5L)); // 输出2,对应路径4->3->5 } }
代码说明
- 全局变量
sumToMinCnt存储前缀和到最小边数的映射,确保同一前缀和只保留能生成最短路径的边数; - DFS遍历中,每次处理节点时先检查是否存在符合条件的前缀和,再将当前前缀和加入映射;
- 回溯时移除当前节点的前缀和,保证不同子树的路径不会被重复计算;
- 时间复杂度为O(N log N),哈希表的平均操作时间为O(1),最坏情况(如哈希冲突)为O(log N),完全适配N=10000的规模。
内容的提问来源于stack exchange,提问作者user25680598
相关产品推荐
相关产品推荐

