树形dp通常使用的dfs实现,关键:树形dp将它的子树信息整合起来,返回给父节点,并由父节点的信息更新该节点的信息。
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
思路
枚举每个节点往左儿子走的最长链和往右儿子走的最长链,这两条链可能会组成最长直径,然后将左右儿子的最长链返回给父节点
实现
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {private int res;public int diameterOfBinaryTree(TreeNode root) {res = 0;dfs(root);return res;}public int dfs(TreeNode root){if (root == null) return 0;int left = dfs(root.left);int right = dfs(root.right);res = Math.max(res, left + right);return Math.max(left + 1, right + 1);}
}
给你这棵「无向树」,请你测算并返回它的「直径」:这棵树上最长简单路径的 边数。
我们用一个由所有「边」组成的数组
edges来表示一棵无向树,其中edges[i] = [u, v]表示节点u和v之间的双向边。树上的节点都已经用
{0, 1, ..., edges.length}中的数做了标记,每个节点上的标记都是独一无二的。
思路:
思路同LC543,不同的是树的存储变为邻接表,并且树不一定是二叉树,因此需要存储每个节点向某个方向走的最大的两个值,记录在数组中,那么树的直径可能为该两个值之和,最后同样将某个节点向下走的最大长度返回给根节点。
实现
class Solution {private List[] g;private int ans;private int[] d1;private int[] d2;public int treeDiameter(int[][] edges) {int n = edges.length;g = new ArrayList[n + 1];Arrays.setAll(g, e -> new ArrayList<>());ans = 0;d1 = new int[n + 1];d2 = new int[n + 1];for (int[] edge : edges){int u = edge[0], v = edge[1];g[u].add(v);g[v].add(u);}dfs(0, -1);return ans;}private int dfs(int u, int start){int res = 0;for (int v : g[u]){ if (v != start){res = dfs(v, u);}if (d2[u] > d1[u] && d1[u] < res){d1[u] = res;}else if(d2[u] <= d1[u] && res > d2[u]){d2[u] = res;}}ans = Math.max(ans, d1[u] + d2[u]);return Math.max(d1[u], d2[u]) + 1;}}
给你一个
n个节点的无向无根图,节点编号为0到n - 1。给你一个整数n和一个长度为n - 1的二维整数数组edges,其中edges[i] = [ai, bi]表示树中节点ai和bi之间有一条边。每个节点都有一个价值。给你一个整数数组
price,其中price[i]是第i个节点的价值。一条路径的 价值和 是这条路径上所有节点的价值之和。
你可以选择树中任意一个节点作为根节点
root。选择root为根的 开销 是以root为起点的所有路径中,价值和 最大的一条路径与最小的一条路径的差值。请你返回所有节点作为根节点的选择中,最大 的 开销 为多少。
思路:
由于价值都是正数,因此价值和最小的一条路径一定只有一个点。那么,「价值和最大的一条路径与最小的一条路径的差值」等价于「去掉路径的一个端点」,因此问题可以转化为问题转换成去掉一个叶子后的最大路径和(这里的叶子严格来说是度为 1 的点,因为根的度数也可能是 1)。
使用树形dp找到去掉一个叶子后的最大路径和:在树上做DFS,递归到最底层的叶子节点,再一层层返回「当前带叶子的路径和」和「当前不带叶子的路径和」更新至根结点,对于根节点而言,答案的可能性有两种:
然后更新「最大带叶子的路径和」和「最大不带叶子的路径和」以及结果。
实现
class Solution {private List[] g;private int[] price;private long ans;public long maxOutput(int n, int[][] edges, int[] price) {this.price = price;g = new ArrayList[n];Arrays.setAll(g, e -> new ArrayList<>());for (var e : edges) {int x = e[0], y = e[1];g[x].add(y);g[y].add(x); // 建树}dfs(0, -1);return ans;}// 返回带叶子的最大路径和,不带叶子的最大路径和private long[] dfs(int x, int fa) {long p = price[x], maxS1 = p, maxS2 = 0;for (var y : g[x])if (y != fa) {var res = dfs(y, x);long s1 = res[0], s2 = res[1];// 前面最大带叶子的路径和 + 当前不带叶子的路径和// 前面最大不带叶子的路径和 + 当前带叶子的路径和ans = Math.max(ans, Math.max(maxS1 + s2, maxS2 + s1));maxS1 = Math.max(maxS1, s1 + p);maxS2 = Math.max(maxS2, s2 + p); // 这里加上 p 是因为 x 必然不是叶子}return new long[]{maxS1, maxS2};}
}作者:灵茶山艾府
链接:https://leetcode.cn/problems/difference-between-maximum-and-minimum-price-sum/solutions/2062782/by-endlesscheng-5l70/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点
root,返回其 最大路径和 。
思路:
使用树形dp找到的最大路径和:在树上做DFS,递归到最底层的叶子节点,再一层层返回「左子树最大路径和」和「右子树最大路径和」的较大值至父结点,那么包含该节点的最大路径和为其「左子树最大路径和」+「右子树最大路径和」+该节点的值,注意当左右子树最大路径和大于0时,才选择。记录最大值返回即可
实现
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {int ans = Integer.MIN_VALUE;public int maxPathSum(TreeNode root) {dfs(root);return ans;}private int dfs(TreeNode root){if (root == null) return 0;int leftGain = Math.max(dfs(root.left), 0);int rightGain = Math.max(dfs(root.right), 0);int pathSum = leftGain + root.val + rightGain;ans = Math.max(ans, pathSum);return root.val + Math.max(leftGain, rightGain);}
}
给你
n个城市,编号为从1到n。同时给你一个大小为n-1的数组edges,其中edges[i] = [ui, vi]表示城市ui和vi之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵 树 。一棵 子树 是城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。
对于
d从1到n-1,请你找到城市间 最大距离 恰好为d的所有子树数目。请你返回一个大小为
n-1的数组,其中第d个元素(下标从 1 开始)是城市间 最大距离 恰好等于d的子树数目。请注意,两个城市间距离定义为它们之间需要经过的边的数目。
思路
由于n≤15n \le 15n≤15,因此可以使用二进制枚举的方法枚举所有的子树。而子树中节点的最大距离,即为子树中两个节点之间的最长路径,也就是该子树的直径,求解子树的直径可以使用DFS或者BFS,先找到树直径的一个端点,然后从该端点出发,找到树的另一个端点,这两个端点之间的路径长度就是树的直径
实现
edges构建出邻接表gmask表示子树,第i位为1表示节点i在子树中,否则表示节点i不在子树中mask mask的二进制表示中只有一个二进制位为1,那么跳过该maskmask的二进制表示中最高位的二进制为1的位置,进行dfs搜索树的直径,如果mask中的节点全部访问过,表示该mask有效,更新结果;否则表示该mask不是合法的子树class Solution {private List[] g;private int mask, vis, diameter;public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {g = new ArrayList[n];Arrays.setAll(g, e -> new ArrayList<>());for (var e : edges) {int x = e[0] - 1, y = e[1] - 1; // 编号改为从 0 开始g[x].add(y);g[y].add(x); // 建树}var ans = new int[n - 1];// 二进制枚举for (mask = 3; mask < 1 << n; ++mask) {if ((mask & (mask - 1)) == 0) continue; // 需要至少两个点vis = diameter = 0;dfs(Integer.numberOfTrailingZeros(mask)); // 从一个在 mask 中的点开始递归if (vis == mask)++ans[diameter - 1];}return ans;}// 求树的直径private int dfs(int x) {vis |= 1 << x; // 标记 x 访问过int maxLen = 0;for (int y : g[x])if ((vis >> y & 1) == 0 && (mask >> y & 1) == 1) { // y 没有访问过且在 mask 中int ml = dfs(y) + 1;diameter = Math.max(diameter, maxLen + ml);maxLen = Math.max(maxLen, ml);}return maxLen;}
}作者:灵茶山艾府
链接:https://leetcode.cn/problems/count-subtrees-with-max-distance-between-cities/solutions/2162612/tu-jie-on3-mei-ju-zhi-jing-duan-dian-che-am2n/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。