数据结构与算法(Java)-树形DP题单

树形DP(灵神笔记)

543 二叉树的直径

543. 二叉树的直径 - 力扣(LeetCode)

给你一棵二叉树的根节点,返回该树的 直径

二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root

两节点之间路径的 长度 由它们之间边数表示。

示例 1:

img

输入:root = [1,2,3,4,5]
输出:3
解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。

示例 2:

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

提示:

  • 树中节点数目在范围 [1, 104]
  • -100 <= Node.val <= 100
/*** 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 ans; public int diameterOfBinaryTree(TreeNode root) {dfs(root);return ans;}private int dfs(TreeNode root) {if (root == null) {return -1;//返回-1 下面+1变成了0}int l_len = dfs(root.left) + 1;//左子树最大链长+1int r_len = dfs(root.right) + 1;//右子树最大链长+1ans = Math.max(ans, l_len + r_len);return Math.max(l_len, r_len);}
}

124 二叉树的最大路径和

124. 二叉树中的最大路径和 - 力扣(LeetCode)

二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

路径和 是路径中各节点值的总和。

给你一个二叉树的根节点 root ,返回其 最大路径和

示例 1:

img

输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6

示例 2:

img

输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42

提示:

  • 树中节点数目范围是 [1, 3 * 104]
  • -1000 <= Node.val <= 1000
/*** 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 ans = Integer.MIN_VALUE;public int maxPathSum(TreeNode root) {dfs(root);return ans;}private int dfs(TreeNode root) {if (root == null) {return 0;//没有结点 和为0}int l_val = dfs(root.left);int r_val = dfs(root.right);ans = Math.max(ans, l_val + r_val + root.val);return Math.max(Math.max(l_val, r_val) + root.val, 0);//负数不选}
}

2246 相邻字符不同的最长路径

2246. 相邻字符不同的最长路径 - 力扣(LeetCode)

给你一棵 (即一个连通、无向、无环图),根节点是节点 0 ,这棵树由编号从 0n - 1n 个节点组成。用下标从 0 开始、长度为 n 的数组 parent 来表示这棵树,其中 parent[i] 是节点 i 的父节点,由于节点 0 是根节点,所以 parent[0] == -1

另给你一个字符串 s ,长度也是 n ,其中 s[i] 表示分配给节点 i 的字符。

请你找出路径上任意一对相邻节点都没有分配到相同字符的 最长路径 ,并返回该路径的长度。

示例 1:

img

输入:parent = [-1,0,0,1,1,2], s = "abacbe"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3 。
可以证明不存在满足上述条件且比 3 更长的路径。 

示例 2:

img

输入:parent = [-1,0,0,0], s = "aabc"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3 。

提示:

  • n == parent.length == s.length
  • 1 <= n <= 105
  • 对所有 i >= 10 <= parent[i] <= n - 1 均成立
  • parent[0] == -1
  • parent 表示一棵有效的树
  • s 仅由小写英文字母组成
class Solution {private List<Integer>[] g;//存储父节点i的子节点private String s;private int ans;public int longestPath(int[] parent, String s) {this.s = s;int n = parent.length;g = new ArrayList[n];Arrays.setAll(g, e -> new ArrayList<>());for (int i = 1; i < n; i++) {//记录父节点的子节点的索引g[parent[i]].add(i);}dfs(0);return ans + 1;//求点的个数,路径长度+1}//计算最大长度private int dfs(int x) {int maxLen = 0;for (int y : g[x]) {int len = dfs(y) + 1;if (s.charAt(x) != s.charAt(y)) {ans = Math.max(ans, maxLen + len);//最长路径maxLen = Math.max(maxLen, len);//左右子树最大长度}}return maxLen;}
}

687 最长同值路径

687. 最长同值路径 - 力扣(LeetCode)

给定一个二叉树的 root ,返回 最长的路径的长度 ,这个路径中的 每个节点具有相同值 。 这条路径可以经过也可以不经过根节点。

两个节点之间的路径长度 由它们之间的边数表示。

示例 1:

img

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

示例 2:

img

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

提示:

  • 树的节点数的范围是 [0, 104]
  • -1000 <= Node.val <= 1000
  • 树的深度将不超过 1000
/*** 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 ans;public int longestUnivaluePath(TreeNode root) {dfs(root);return ans;}private int dfs(TreeNode root) {if (root == null) {return -1;}int l_len = dfs(root.left) + 1;int r_len = dfs(root.right) + 1;if (root.left != null && root.left.val != root.val) {l_len = 0;}if (root.right != null && root.right.val != root.val) {r_len = 0;}ans = Math.max(ans, l_len + r_len);return Math.max(l_len, r_len);}
}

1617 统计子树中城市之间最大距离

参考题解:1617. 统计子树中城市之间最大距离 - 力扣(LeetCode)

给你 n 个城市,编号为从 1n 。同时给你一个大小为 n-1 的数组 edges ,其中 edges[i] = [ui, vi] 表示城市 uivi 之间有一条双向边。题目保证任意城市之间只有唯一的一条路径。换句话说,所有城市形成了一棵

一棵 子树 是城市的一个子集,且子集中任意城市之间可以通过子集中的其他城市和边到达。两个子树被认为不一样的条件是至少有一个城市在其中一棵子树中存在,但在另一棵子树中不存在。

对于 d1n-1 ,请你找到城市间 最大距离 恰好为 d 的所有子树数目。

请你返回一个大小为 n-1 的数组,其中第 d 个元素(下标从 1 开始)是城市间 最大距离 恰好等于 d 的子树数目。

请注意,两个城市间距离定义为它们之间需要经过的边的数目。

示例 1:

img

输入:n = 4, edges = [[1,2],[2,3],[2,4]]
输出:[3,4,0]
解释:
子树 {1,2}, {2,3} 和 {2,4} 最大距离都是 1 。
子树 {1,2,3}, {1,2,4}, {2,3,4} 和 {1,2,3,4} 最大距离都为 2 。
不存在城市间最大距离为 3 的子树。

示例 2:

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

示例 3:

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

提示:

  • 2 <= n <= 15
  • edges.length == n-1
  • edges[i].length == 2
  • 1 <= ui, vi <= n
  • 题目保证 (ui, vi) 所表示的边互不相同。
class Solution {private List<Integer>[] g;private boolean[] inSet, vis;//inSet是选出来的树(城市) vis记录的是这个城市(树)的直径private int[] ans;private int n, diameter;//定义n和直径public int[] countSubgraphsForEachDiameter(int n, int[][] edges) {this.n = n;g = new ArrayList[n];Arrays.setAll(g, e -> new ArrayList<>());for (var e : edges) {int x = e[0] - 1;int y = e[1] - 1;g[x].add(y);g[y].add(x);//建树}ans = new int[n - 1];inSet = new boolean[n];f(0);return ans;}private void f(int i) {if (i == n) {for (int v = 0; v < n; v++) {if (inSet[v]) {vis = new boolean[n];diameter = 0;dfs(v);break;}}if (diameter > 0 && Arrays.equals(vis, inSet)) {ans[diameter - 1]++;}return;}//不选城市if(i + 1);//选城市iinSet[i] = true;f(i + 1);inSet[i] = false;}//树的直径private int dfs(int x) {vis[x] = true;int maxLen = 0;for (int y : g[x]) {if (!vis[y] && inSet[y]) {int len = dfs(y) + 1;diameter = Math.max(diameter, maxLen + len);maxLen = Math.max(maxLen, len);}}return maxLen;}
}

2538 最大价值和与最小价值和的差值

参考题解:2538. 最大价值和与最小价值和的差值 - 力扣(LeetCode)

给你一个 n 个节点的无向无根图,节点编号为 0n - 1 。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 aibi 之间有一条边。

每个节点都有一个价值。给你一个整数数组 price ,其中 price[i] 是第 i 个节点的价值。

一条路径的 价值和 是这条路径上所有节点的价值之和。

你可以选择树中任意一个节点作为根节点 root 。选择 root 为根的 开销 是以 root 为起点的所有路径中,价值和 最大的一条路径与最小的一条路径的差值。

请你返回所有节点作为根节点的选择中,最大开销 为多少。

示例 1:

img

输入:n = 6, edges = [[0,1],[1,2],[1,3],[3,4],[3,5]], price = [9,8,7,6,10,5]
输出:24
解释:上图展示了以节点 2 为根的树。左图(红色的节点)是最大价值和路径,右图(蓝色的节点)是最小价值和路径。
- 第一条路径节点为 [2,1,3,4]:价值为 [7,8,6,10] ,价值和为 31 。
- 第二条路径节点为 [2] ,价值为 [7] 。
最大路径和与最小路径和的差值为 24 。24 是所有方案中的最大开销。

示例 2:

img

输入:n = 3, edges = [[0,1],[1,2]], price = [1,1,1]
输出:2
解释:上图展示了以节点 0 为根的树。左图(红色的节点)是最大价值和路径,右图(蓝色的节点)是最小价值和路径。
- 第一条路径包含节点 [0,1,2]:价值为 [1,1,1] ,价值和为 3 。
- 第二条路径节点为 [0] ,价值为 [1] 。
最大路径和与最小路径和的差值为 2 。2 是所有方案中的最大开销。

提示:

  • 1 <= n <= 105
  • edges.length == n - 1
  • 0 <= ai, bi <= n - 1
  • edges 表示一棵符合题面要求的树。
  • price.length == n
  • 1 <= price[i] <= 105
class Solution {private List<Integer>[] 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 father) {// maxS1前面最大带叶子的路径和 maxS2前面最大不带叶子的路径和long p = price[x], maxS1 = p, maxS2 = 0;for (var y : g[x]) {if (y != father) {var res = dfs(y, x);// s1当前不带叶子的路径和 s2当前带叶子的路径和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);// x必然不是叶子}}return new long[]{maxS1, maxS2};}
}

337 打家劫舍三

337. 打家劫舍 III - 力扣(LeetCode)

小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root

除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。

给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额

示例 1:

img

输入: root = [3,2,3,null,3,null,1]
输出: 7 
解释: 小偷一晚能够盗取的最高金额 3 + 3 + 1 = 7

示例 2:

img

输入: root = [3,4,5,1,3,null,1]
输出: 9
解释: 小偷一晚能够盗取的最高金额 4 + 5 = 9

提示:

  • 树的节点数在 [1, 104] 范围内
  • 0 <= Node.val <= 104
/*** 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 {public int rob(TreeNode root) {int[] ans = dfs(root);return Math.max(ans[0], ans[1]);}private int[] dfs(TreeNode root) {if (root == null) {return new int[]{0,0};}int[] left = dfs(root.left);int[] right = dfs(root.right);int rob = left[1] + right[1] + root.val;//选根节点//不选根节点int notRob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);return new int[]{rob, notRob};}
}

1377 T秒后青蛙的位置

1377. T 秒后青蛙的位置 - 力扣(LeetCode)

参考题解:1377. T 秒后青蛙的位置 - 力扣(LeetCode)

建图(树)模版(以1377为例)

public static void main(String[] args) {int n = 7;int[][] edges = new int[][]{{1,2},{1,3},{1,7},{2,4},{2,6},{3,5}};List<Integer>[] g = new ArrayList[n + 1];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); // 建树}}

给你一棵由 n 个顶点组成的无向树,顶点编号从 1n。青蛙从 顶点 1 开始起跳。规则如下:

  • 在一秒内,青蛙从它所在的当前顶点跳到另一个 未访问 过的顶点(如果它们直接相连)。
  • 青蛙无法跳回已经访问过的顶点。
  • 如果青蛙可以跳到多个不同顶点,那么它跳到其中任意一个顶点上的机率都相同。
  • 如果青蛙不能跳到任何未访问过的顶点上,那么它每次跳跃都会停留在原地。

无向树的边用数组 edges 描述,其中 edges[i] = [ai, bi] 意味着存在一条直接连通 aibi 两个顶点的边。

返回青蛙在 t 秒后位于目标顶点 target 上的概率。与实际答案相差不超过 10-5 的结果将被视为正确答案。

示例 1:

img

输入:n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 2, target = 4
输出:0.16666666666666666 
解释:上图显示了青蛙的跳跃路径。青蛙从顶点 1 起跳,第 1 秒 有 1/3 的概率跳到顶点 2 ,然后第 2 秒 有 1/2 的概率跳到顶点 4,因此青蛙在 2 秒后位于顶点 4 的概率是 1/3 * 1/2 = 1/6 = 0.16666666666666666 。 

示例 2:

img

输入:n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 1, target = 7
输出:0.3333333333333333
解释:上图显示了青蛙的跳跃路径。青蛙从顶点 1 起跳,有 1/3 = 0.3333333333333333 的概率能够 1 秒 后跳到顶点 7 。 

提示:

  • 1 <= n <= 100
  • edges.length == n - 1
  • edges[i].length == 2
  • 1 <= ai, bi <= n
  • 1 <= t <= 50
  • 1 <= target <= n
class Solution {//自底向上查找public double frogPosition(int n, int[][] edges, int t, int target) {List<Integer>[] g = new ArrayList[n + 1];Arrays.setAll(g, e -> new ArrayList<>());g[1].add(0);// 减少额外判断for (var e : edges) {int x = e[0], y = e[1];g[x].add(y);g[y].add(x); // 建树}long prod = dfs(g, target, 1, 0, t);return prod != 0 ? 1.0 / prod : 0;}private long dfs(List<Integer>[] g, int target, int x, int father, int leftT) {//t秒后在targetif (leftT == 0) {return x == target ? 1 : 0;}//target是叶子停在原地if (x == target) {return g[x].size() == 1 ? 1 : 0;}for (int y : g[x]) {if (y != father) {long prod = dfs(g, target, y, x, leftT - 1);if (prod != 0) {return prod * (g[x].size() - 1);// 乘上儿子的个数}}}return 0;// 未找到 target}
}

2646 最小化旅行的价格总和

参考题解:2646. 最小化旅行的价格总和 - 力扣(LeetCode)

现有一棵无向、无根的树,树中有 n 个节点,按从 0n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] 表示树中节点 aibi 之间存在一条边。

每个节点都关联一个价格。给你一个整数数组 price ,其中 price[i] 是第 i 个节点的价格。

给定路径的 价格总和 是该路径上所有节点的价格之和。

另给你一个二维整数数组 trips ,其中 trips[i] = [starti, endi] 表示您从节点 starti 开始第 i 次旅行,并通过任何你喜欢的路径前往节点 endi

在执行第一次旅行之前,你可以选择一些 非相邻节点 并将价格减半。

返回执行所有旅行的最小价格总和。

示例 1:

img

输入:n = 4, edges = [[0,1],[1,2],[1,3]], price = [2,2,10,6], trips = [[0,3],[2,1],[2,3]]
输出:23
解释:
上图表示将节点 2 视为根之后的树结构。第一个图表示初始树,第二个图表示选择节点 0 、2 和 3 并使其价格减半后的树。
第 1 次旅行,选择路径 [0,1,3] 。路径的价格总和为 1 + 2 + 3 = 6 。
第 2 次旅行,选择路径 [2,1] 。路径的价格总和为 2 + 5 = 7 。
第 3 次旅行,选择路径 [2,1,3] 。路径的价格总和为 5 + 2 + 3 = 10 。
所有旅行的价格总和为 6 + 7 + 10 = 23 。可以证明,23 是可以实现的最小答案。

示例 2:

img

输入:n = 2, edges = [[0,1]], price = [2,2], trips = [[0,0]]
输出:1
解释:
上图表示将节点 0 视为根之后的树结构。第一个图表示初始树,第二个图表示选择节点 0 并使其价格减半后的树。 
第 1 次旅行,选择路径 [0] 。路径的价格总和为 1 。 
所有旅行的价格总和为 1 。可以证明,1 是可以实现的最小答案。

提示:

  • 1 <= n <= 50
  • edges.length == n - 1
  • 0 <= ai, bi <= n - 1
  • edges 表示一棵有效的树
  • price.length == n
  • price[i] 是一个偶数
  • 1 <= price[i] <= 1000
  • 1 <= trips.length <= 100
  • 0 <= starti, endi <= n - 1
class Solution {private List<Integer>[] g;private int[] price, cnt;private int end;public int minimumTotalPrice(int n, int[][] edges, int[] price, int[][] trips) {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);}this.price = price;cnt = new int[n];for (var t : trips) {end = t[1];path(t[0], -1);}int[] p = dfs(0, -1);return Math.min(p[0], p[1]);}private boolean path(int x, int father) {if (x == end) {cnt[x]++;//统计从start到end的路径上的点经过了多少次return true;//找到终点}for (var y : g[x]) {if (y != father && path(y, x)) {cnt[x]++;return true;}}return false;//没找到终点}private int[] dfs(int x, int father) {int notSelect = price[x] * cnt[x];//x不变int select = notSelect / 2;//x减半for (var y : g[x]) {if (y != father) {int[] p = dfs(y, x);//计算儿子y 不变/减半的最小价值总和notSelect += Math.min(p[0], p[1]);select += p[0];}}return new int[]{notSelect, select};}
}

2467 树上最大得分和路径

参考题解:2467. 树上最大得分和路径 - 力扣(LeetCode)

一个 n 个节点的无向树,节点编号为 0n - 1 ,树的根结点是 0 号节点。给你一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ai, bi] ,表示节点 aibi 在树中有一条边。

在每一个节点 i 处有一扇门。同时给你一个都是偶数的数组 amount ,其中 amount[i] 表示:

  • 如果 amount[i] 的值是负数,那么它表示打开节点 i 处门扣除的分数。
  • 如果 amount[i] 的值是正数,那么它表示打开节点 i 处门加上的分数。

游戏按照如下规则进行:

  • 一开始,Alice 在节点 0 处,Bob 在节点 bob 处。
  • 每一秒钟,Alice 和 Bob 分别 移动到相邻的节点。Alice 朝着某个 叶子结点 移动,Bob 朝着节点 0 移动。
  • 对于他们之间路径上的每一个节点,Alice 和 Bob 要么打开门并扣分,要么打开门并加分。注意:
    • 如果门 已经打开 (被另一个人打开),不会有额外加分也不会扣分。
    • 如果 Alice 和 Bob 同时 到达一个节点,他们会共享这个节点的加分或者扣分。换言之,如果打开这扇门扣 c 分,那么 Alice 和 Bob 分别扣 c / 2 分。如果这扇门的加分为 c ,那么他们分别加 c / 2 分。
  • 如果 Alice 到达了一个叶子结点,她会停止移动。类似的,如果 Bob 到达了节点 0 ,他也会停止移动。注意这些事件互相 独立 ,不会影响另一方移动。

请你返回 Alice 朝最优叶子结点移动的 最大 净得分。

示例 1:

img

输入:edges = [[0,1],[1,2],[1,3],[3,4]], bob = 3, amount = [-2,4,2,-4,6]
输出:6
解释:
上图展示了输入给出的一棵树。游戏进行如下:
- Alice 一开始在节点 0 处,Bob 在节点 3 处。他们分别打开所在节点的门。Alice 得分为 -2 。
- Alice 和 Bob 都移动到节点 1 。因为他们同时到达这个节点,他们一起打开门并平分得分。Alice 的得分变为 -2 + (4 / 2) = 0 。
- Alice 移动到节点 3 。因为 Bob 已经打开了这扇门,Alice 得分不变。Bob 移动到节点 0 ,并停止移动。
- Alice 移动到节点 4 并打开这个节点的门,她得分变为 0 + 6 = 6 。
现在,Alice 和 Bob 都不能进行任何移动了,所以游戏结束。
Alice 无法得到更高分数。

示例 2:

img

输入:edges = [[0,1]], bob = 1, amount = [-7280,2350]
输出:-7280
解释:
Alice 按照路径 0->1 移动,同时 Bob 按照路径 1->0 移动。
所以 Alice 只打开节点 0 处的门,她的得分为 -7280 。

提示:

  • 2 <= n <= 105
  • edges.length == n - 1
  • edges[i].length == 2
  • 0 <= ai, bi < n
  • ai != bi
  • edges 表示一棵有效的树。
  • 1 <= bob < n
  • amount.length == n
  • amount[i] 是范围 [-104, 104] 之间的一个 偶数
class Solution {private int[] bobTime;private int[] amount;private int ans = Integer.MIN_VALUE;public int mostProfitablePath(int[][] edges, int bob, int[] amount) {int n = amount.length;bobTime = new int[n];Arrays.fill(bobTime, n);this.amount = amount;List<Integer>[] g = new ArrayList[n + 1];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); // 建树}dfsBob(g, bob, -1, 0);g[0].add(-1);//防止把根节点当成叶子dfsAlice(g, 0, -1, 0, 0);return ans;}public boolean dfsBob(List<Integer>[] g, int p, int father, int time) {if (p == 0) {//到达0点bobTime[p] = time;return true;} else {boolean found0 = false;for (int e : g[p]) {if (e != father && dfsBob(g, e, p, time + 1)) {found0 = true;break;}}if (found0) {//到达0点bobTime[p] = time;}return found0;}}//total表示路径得分public void dfsAlice(List<Integer>[] g, int p, int father, int time, int total) {if (bobTime[p] == time) {//两人相遇total += amount[p] / 2;}if (bobTime[p] > time) {total += amount[p];}//找到叶子结点 更新答案if (g[p].size() == 1) {ans = Math.max(ans, total);return;}for (int e : g[p]) {if (e != father) {dfsAlice(g, e, p, time + 1, total);}}}
}

968 监控二叉树

参考:树形 DP:监控二叉树【基础算法精讲 25】_哔哩哔哩_bilibili

给定一个二叉树,我们在树的节点上安装摄像头。

节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。

计算监控树的所有节点所需的最小摄像头数量。

示例 1:

img

输入:[0,0,null,0,0]
输出:1
解释:如图所示,一台摄像头足以监控所有节点。

示例 2:

img

输入:[0,0,null,0,null,0,null,null,0]
输出:2
解释:需要至少两个摄像头来监视树的所有节点。 上图显示了摄像头放置的有效位置之一。

提示:

  1. 给定树的节点数的范围是 [1, 1000]
  2. 每个节点的值都是 0。

思路:

  • 蓝色:安装摄像头
  • 黄色:不安装摄像头,父节点安装摄像头
  • 红色:不安装摄像头,至少一个儿子安装摄像头

蓝色=min(左蓝 左黄 左红)+min(右蓝 右黄 右红)+1

黄色=min(左蓝 左红)+min(右蓝 右红)

红色=min(左蓝+右红 左红+右蓝 左蓝+右蓝)

/*** 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 {public int minCameraCover(TreeNode root) {int[] res = dfs(root);return Math.min(res[0], res[2]);}private int[] dfs(TreeNode root) {if (root == null) {return new int[]{Integer.MAX_VALUE / 2, 0, 0};}int[] left = dfs(root.left);int[] right = dfs(root.right);int choose = Math.min(Math.min(left[0], left[1]), left[2]) + Math.min(Math.min(right[0], right[1]), right[2]) + 1;int byFather = Math.min(left[0], left[2]) + Math.min(right[0], right[2]);int byChildren = Math.min(Math.min(left[0] + right[2], left[2] + right[0]), left[0] + right[0]);return new int[]{choose, byFather, byChildren};}
}

此外,我们发现红色的计算结果太多了(这一层有n个结点,总共有2^n-1种情况),那么我们该如何优化呢?

假设我们去掉必须选一个蓝色的条件,式子变为min(蓝1,红1)+min(蓝2,红2)+min(蓝3,红3)

例如,min(5,2)+min(7,6)+min(5,1),要想选择一个蓝色,必须将一个红色改为蓝色,因此将6改为7最为合适

由此,我们知道需要找到min(蓝-红),所以得到如下的式子:

黄色=min(蓝1,红1)+min(蓝2,红2)+min(蓝3,红3)

红色=黄色+max(0,min(蓝1-红2 蓝2-红2 蓝3-红3))

蓝色=min(蓝1 黄1)+min(蓝2 黄2)+min(蓝3 黄3)+cost[x]

cost[x]为花费的成本

蓝色一定大于红色,所以第三个蓝色的式子不用比较红色,这就是如下的题目

保安站岗(洛谷)

SDOI2006\ 保安站岗 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;public class Main {static int[] cost;static List<Integer>[] g;public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();cost = new int[n + 1];g = new ArrayList[n + 1];//建树Arrays.setAll(g, e -> new ArrayList<>());for (; n > 0; n--) {int v = sc.nextInt();v--;cost[v] = sc.nextInt();int m = sc.nextInt();for (; m > 0; m--) {int w = sc.nextInt();w--;g[v].add(w);g[w].add(v);}}int[] ans = dfs(0, -1);int choose = ans[0];int bySon = ans[2];System.out.println(Math.min(choose, bySon));}static int[] dfs(int x, int father) {int choose = cost[x];int byFather = 0;int minExtra = Integer.MAX_VALUE / 2;for (var y : g[x]) {if (y == father) {continue;}int[] arr = dfs(y, x);int c = arr[0];//蓝色int byFa = arr[1];//黄色int bySon = arr[2];//红色choose += Math.min(c, byFa);byFather += Math.min(c, bySon);minExtra = Math.min(minExtra, c - bySon);}return new int[]{choose, byFather, byFather + Math.max(0, minExtra)};}
}

LCP 34 二叉树染色

LCP 34. 二叉树染色 - 力扣(LeetCode)

小扣有一个根结点为 root 的二叉树模型,初始所有结点均为白色,可以用蓝色染料给模型结点染色,模型的每个结点有一个 val 价值。小扣出于美观考虑,希望最后二叉树上每个蓝色相连部分的结点个数不能超过 k 个,求所有染成蓝色的结点价值总和最大是多少?

示例 1:

输入:root = [5,2,3,4], k = 2

输出:12

解释:结点 5、3、4 染成蓝色,获得最大的价值 5+3+4=12image.png

示例 2:

输入:root = [4,1,3,9,null,null,2], k = 2

输出:16

解释:结点 4、3、9 染成蓝色,获得最大的价值 4+3+9=16image.png

提示:

  • 1 <= k <= 10
  • 1 <= val <= 10000
  • 1 <= 结点数量 <= 10000

思路

如果根节点是白色,那么返回左边的最大和和右边的最大和 即f[0]=maxleft+maxright

如果根节点是蓝色

当i=1时,两个儿子都为白色 此时f[i]=root.val+f[0](左)+f[0](右)

当i=2时,一个儿子为蓝色

  • 如果k=2,可以分为 左 0 右 1、左 1 右 0 此时 f[i]=max(root.val+f[0](左)+f[1](右),root.val+f[1](左)+f[0](右))
  • 如果k=3,可分为 左 0 右 2、左 1 右 1、左 2 右 0 三种情况;

最后得到 f[i]=max(val+f[j](左)+f[i-j-1](右))

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {public int maxValue(TreeNode root, int k) {int[] ans = dfs(root, k);int max = Integer.MIN_VALUE;for (int a : ans) {max = Math.max(max, a);}return max;}private int[] dfs(TreeNode root, int k) {int[] f = new int[k + 1];if (root == null) {return f;}int[] left = dfs(root.left, k);int[] right = dfs(root.right, k);int maxLeft = Integer.MIN_VALUE;int maxRight = Integer.MIN_VALUE;for (int i = 0; i < k + 1; i++) {maxLeft = Math.max(maxLeft, left[i]);maxRight = Math.max(maxRight, right[i]);}f[0] = maxLeft + maxRight;for (int i = 0; i < k + 1; i++) {for (int j = 0; j < i; j++) {f[i] = Math.max(f[i], root.val + left[j] + right[i - j - 1]);}}return f;}
}

LCP 64 二叉树灯饰

参考视频:动态规划 树形 DP【力扣杯2022秋·个人赛】_哔哩哔哩_bilibili

「力扣嘉年华」的中心广场放置了一个巨型的二叉树形状的装饰树。每个节点上均有一盏灯和三个开关。节点值为 0 表示灯处于「关闭」状态,节点值为 1 表示灯处于「开启」状态。每个节点上的三个开关各自功能如下:

  • 开关 1:切换当前节点的灯的状态;
  • 开关 2:切换 以当前节点为根 的子树中,所有节点上的灯的状态;
  • 开关 3:切换 当前节点及其左右子节点(若存在的话) 上的灯的状态;

给定该装饰的初始状态 root,请返回最少需要操作多少次开关,可以关闭所有节点的灯。

示例 1:

输入:root = [1,1,0,null,null,null,1]
输出:2
解释:以下是最佳的方案之一,如图所示

示例1

示例 2:

输入:root = [1,1,1,1,null,null,1]
输出:1
解释:以下是最佳的方案,如图所示

示例2

示例 3:

输入:root = [0,null,0]
输出:0
解释:无需操作开关,当前所有节点上的灯均已关闭

提示:

  • 1 <= 节点个数 <= 10^5
  • 0 <= Node.val <= 1

思路:

任意一个结点,会受到哪些影响:

  1. 祖先结点的开关2的切换次数 偶数=不切换 奇数=切换
  2. 父节点是否切换了开关3

状态:(当前结点 开关2的切换次数的奇偶性 父节点是否开关3)

返回:当前状态,最少需要操作的次数

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode(int x) { val = x; }* }*/
class Solution {private HashMap<TreeNode, int[][]> map;public int closeLampInTree(TreeNode root) {map = new HashMap<>();return dfs(root, false, false);}private int dfs(TreeNode root, boolean switch2_odd, boolean switch3) {if (root == null) return 0;//记忆化搜索int x = switch2_odd ? 1 : 0;int y = switch3 ? 1 : 0;int[][] temp = new int[2][2];if (map.containsKey(root)) {temp = map.get(root);if (temp[x][y] > 0) {return temp[x][y];}} else {map.put(root, temp);}int res = Integer.MAX_VALUE / 2;//灯打开 开关2和开关3抵消 灯开//灯关闭 开关2和开关3没有抵消 灯开if ((root.val == 1) == (switch2_odd == switch3)) {int res1 = dfs(root.left, switch2_odd, false) + dfs(root.right, switch2_odd, false) + 1; int res2 = dfs(root.left, !switch2_odd, false) + dfs(root.right, !switch2_odd, false) + 1; int res3 = dfs(root.left, switch2_odd, true) + dfs(root.right, switch2_odd, true) + 1; int res123 = dfs(root.left, !switch2_odd, true) + dfs(root.right, !switch2_odd, true) + 3;res = Math.min(Math.min(res1, res2), Math.min(res3, res123)); } else {//灯关闭 不开开关 或者 开两个开关int res0 = dfs(root.left, switch2_odd, false) + dfs(root.right, switch2_odd, false);int res12 = dfs(root.left, !switch2_odd, false) + dfs(root.right, !switch2_odd, false) + 2;int res13 = dfs(root.left, switch2_odd, true) + dfs(root.right, switch2_odd, true) + 2;int res23 = dfs(root.left, !switch2_odd, true) + dfs(root.right, !switch2_odd, true) + 2;res = Math.min(Math.min(res0, res12), Math.min(res13, res23));}temp[x][y] = res;return res;}
}

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

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

相关文章

【SK-learn学习】1.16 概率校准

一、说明 概率校准&#xff0c;指的是对于分类器而言&#xff0c;对应多种类别&#xff0c;概率最大就将样本归入&#xff0c;这个事实没有考虑置信度的问题。sklearn的calibration就是指的这种情形&#xff0c;参考本文。 二、关于sklearn.calibration的概念 执行分类时&#…

算法基础之食物链

食物链 核心思想&#xff1a;带权并查集 用距根节点和距离表示与根节点的关系 求距离 #include<iostream>using namespace std;const int N50010;int n,m;int p[N],d[N];//找到祖宗节点(路径压缩) 并求出对应距离int find(int x){if(p[x]!x){int up[x]; //保存旧父节点…

C#通过NPOI 读、写Excel数据;合并单元格、简单样式修改;通过读取已有的Excel模板另存为文件

文章目录 1 需要引用的DLL2 调用示例3 工具类 1 需要引用的DLL 2 调用示例 public static void WriteExcel() {string templateFile "F:\12312\excel.xlsx"; // 文件必须存在string outFile "F:\12312\" DateTime.Now.ToString("yyyyMMddHHmmssff…

快速了解Spring AOP的概念及使用

文章目录 1. AOP概念1.1 什么是AOP&#xff1f;1.2 什么是Spring AOP&#xff1f; 2. Spring AOP的使用2.1 引入Spring AOP依赖2.2 编写AOP程序 3. Spring AOP详解3.1 Spring AOP核心概念1. 切点&#xff08;Pointcut&#xff09;2. 连接点&#xff08;Join Point&#xff09;3…

【软件测试】银行测试项目VS常规项目有什么区别?怎么做?

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 银行测试项目跟常…

20世纪30年代的大危机

背景 1929年9月&#xff0c;美国财政部部长安德鲁梅隆向公众保证“现在没有担心的理由&#xff0c;这一繁荣的高潮将会继续下去”。 当时流行的一首儿歌&#xff1a;“梅隆拉响汽笛&#xff0c;胡佛敲起钟&#xff0c;华尔街发出信号&#xff0c;美国往地狱里冲&#xff01;”…

水文监测遥测终端机RTU

水文监测是指通过测量和监测水体的物理、化学和生物性质&#xff0c;以获得关于水资源质量和水体环境状态的信息的过程。包括地下水位、地表水位、河流水位等&#xff0c;从而能够及时采取措施应对水资源过度开采、污染等问题&#xff0c;保障水资源的可持续利用。其次&#xf…

Kubernetes入门篇

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容Kubernetes是什么&#xff1f;为什么需要 Kubernetes&#xff0c;它能做什么&#xff1f;Kubernetes 版本进化Kubernetes 概念Kubernetes 中 3 个重要概念 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介…

JAVA文件IO, File类, 字符流,字节流

文章目录 文件IO1. File2. IO流2.1 字符流2.1.1 Reader2.1.2 Writer 2.2 字节流2.2.1 InputStream2.2.2 FileInputStream2.2.3 利用Scanner进行字符读取2.2.4 OutputStream 文件IO I: Input, 从硬盘往内存读数据 O: Output, 从内存往硬盘输出数据 1. File Java 中通过 java…

SAP_ABAP_编程基础_二进制文件_SMW0中上传与下载

SAP ABAP 顾问&#xff08;开发工程师&#xff09;能力模型_Terry谈企业数字化的博客-CSDN博客文章浏览阅读448次。目标&#xff1a;基于对SAP abap 顾问能力模型的梳理&#xff0c;给一年左右经验的abaper 快速成长为三年经验提供超级燃料&#xff01;https://blog.csdn.net/j…

VT-VSPA1-1X比例压力阀控制板

替代力士乐同型号,可以完全互换使用&#xff1b;适用于力士乐系列所有无电位置反馈的直动式和先导式比例压力阀的控制&#xff1b;外置欧板式连接&#xff0c;VT-VSPA1-1X型放大器配套支架型号&#xff1a;VT-3002-1X/32D或VT-3002-2X/32D&#xff1b; VT-VSPA2-1-1X/T1、VT-V…

Joint Bilateral Upsampling

Abstract 图像分析和增强任务&#xff08;例如色调映射、着色、立体深度和蒙太奇&#xff09;通常需要在像素网格上计算解决方案&#xff08;例如&#xff0c;曝光、色度、视差、标签&#xff09;。计算和内存成本通常要求在下采样图像上运行较小的解决方案。尽管通用上采样方…