数据结构与算法(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.mzph.cn/news/172225.shtml

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

相关文章

使用 Java 客户端通过 HTTPS 连接到 Easysearch

Easysearch 一直致力于提高易用性&#xff0c;这也是我们的核心宗旨&#xff0c;然而之前一直没有官方的 Java 客户端&#xff0c;也对用户使用造成了一些困扰&#xff0c;现在&#xff0c;我们正式发布了第一个 Java 客户端 Easysearch-client:1.0.1。 这一里程碑式的更新为开…

成为AI产品经理——TPR、FPR、ROC、AUC

目录 一、PR图、BEP 1.PR图 2.BEP 二、灵敏度、特异度 1.灵敏度 2.特异度 三、真正率、假正率 1.真正率 2.假正率 三、ROC、AUC 1.ROC 2.AUC 四、KS值 一、PR图、BEP 1.PR图 二分类问题模型通常输出的是一个概率值&#xff0c;我们需要设定一个阈值&#xff…

Android aidl的简单使用

一.服务端 1.创建aidl文件&#xff0c;然后记得build下生成java文件 package com.example.aidlservice31;// Declare any non-default types here with import statementsinterface IMyAidlServer {// 接收一个字符串参数void setData(String value);// 返回一个字符串String …

数据结构与算法之贪心: LeetCode 122. 买卖股票的最佳时机II (Typescript版)

买卖股票的最佳时机II 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能获得的 …

mybatis配置文件中配置类型别名的方式

在MyBatis配置文件&#xff08;通常是mybatis-config.xml&#xff09;中&#xff0c;可以通过以下方式配置类型别名&#xff1a; 1. 使用typeAliases元素配置全局类型别名 <configuration> <typeAliases> <typeAlias alias"YourAlias" type"…

简单订单和支付业务的相关流程

1、订单创建、支付及订单处理流程图 2、创建HTTP客户端工具类 Slf4j public class HttpclientUtil {//类中定义了一个私有静态成员变量instance&#xff0c;并且将其初始化为HttpclientUtil类的一个实例&#xff0c;用于实现单例模式。private static HttpclientUtil instance…

单片机学习6——定时器/计数功能的概念

在8051单片机中有两个定时器/计数器&#xff0c;分别是定时器/计数器0和定时器/计数器1。 T/C0: 定时器/计数器0 T/C1: 定时器/计数器1 T0: 定时器0 T1: 定时器1 C0: 计数器0 C1: 计数器1 如果是对内部振荡源12分频的脉冲信号进行计数&#xff0c;对每个机器周期计数&am…

基于springboot+vue的学生宿舍管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

时间序列预测 — LSTM实现单变量风电滚动预测(Keras)

目录 1 数据处理 1.1 数据集简介 1.2 数据集处理 2 模型训练与预测 2.1 模型训练 2.2 模型滚动预测 2.3 结果可视化 1 数据处理 1.1 数据集简介 实验数据集采用数据集5&#xff1a;风电机组运行数据集&#xff08;下载链接&#xff09;&#xff0c;包括风速、风向、温…

JC/T 456-2015 陶瓷马赛克检测

陶瓷马赛克是指可拼接贴成联的或可单独铺贴的小规格陶瓷砖&#xff0c;可用于墙面和地面起保护及装饰作用。 JC/T 456-2015 陶瓷马赛克测试&#xff1a; 测试项目 测试方法 尺寸允许偏差 JC/T 456 外观质量 JC/T 456 吸水率 GB/T 3810.3 耐磨性 GB/T 3810.6 GB/T 381…

编程语言发展史:JavaScript语言的发展和应用

预计更新 第一部分&#xff1a;早期编程语言 1.1布尔代数和机器语言 1.2汇编语言的出现和发展 1.3高级语言的兴起 第二部分&#xff1a;主流编程语言 1.1 C语言的诞生及其影响 1.2 C语言的发展和应用 1.3 Java语言的出现和发展 1.4 Python语言的兴起和特点 1.5 JavaScript语言…

华为OD机试 - 游戏分组(Java JS Python C)

题目描述 部门准备举办一场王者荣耀表演赛,有 10 名游戏爱好者参与,分为两队,每队 5 人。 每位参与者都有一个评分,代表着他的游戏水平。为了表演赛尽可能精彩,我们需要把 10 名参赛者分为示例尽量相近的两队。 一队的实力可以表示为这一队 5 名队员的评分总和。 现在…

使用Rust开发小游戏

本文是对 使用 Rust 开发一个微型游戏【已完结】[1]的学习与记录. cargo new flappy 在Cargo.toml的[dependencies]下方增加: bracket-lib "~0.8.7" main.rs中: use bracket_lib::prelude::*;struct State {}impl GameState for State { fn tick(&mut self,…

每日一题--相交链表

离思五首-元稹 曾经沧海难为水&#xff0c;除却巫山不是云。 取次花丛懒回顾&#xff0c;半缘修道半缘君。 目录 题目描述&#xff1a; 思路分析&#xff1a; 方法及时间复杂度&#xff1a; 法一 计算链表长度(暴力解法) 法二 栈 法三 哈希集合 法四 map或unordered_map…

【面经八股】搜广推方向:常见面试题(二)

【面经&八股】搜广推方向:常见面试题(二) 文章目录 【面经&八股】搜广推方向:常见面试题(二)1. FTRL 是什么?(Follow The Regularized Leader)2. 梯度下降方法3. 推荐系统中常见的Embedding方法有哪些?4. Embedding与推荐系统有哪些结合5. FM 和 FFM6. FNN7. 深…

一文学会使用React-Router v6

文章目录 基本格式createBrowserRouterType Declarationroutesbasenamefuture and window ... 省略 <RouterProvider>fall上述文本提到 createBrowserRouter 函数的一个特性&#xff1a;在非服务器端渲染应用程序时&#xff0c;当它挂载时会初始化所有匹配的路由加载器。…

解决hbuilder使用android studio模拟器不能热更新

hbuilder使用android studio模拟器编&#xff0c;在编写代码时&#xff0c;不能热更新&#xff0c;总是需要重启虚拟机中的程序&#xff0c;hbuilderx的版本是3.1.22&#xff0c;android studio的版本是4.2.2 同时在hbuilderx中出现如下报错信息&#xff1a; 报错信息&#x…

HuggingFace学习笔记--AutoModel的使用

1--AutoModel的使用 官方文档 AutoModel 用于加载模型&#xff1b; 1-1--简单Demo 测试代码&#xff1a; from transformers import AutoTokenizer, AutoModelif __name__ "__main__":checkpoint "distilbert-base-uncased-finetuned-sst-2-english"t…

java进阶---多态

多态的概述 什么是多态 同一个对象,在不同时刻表现出来的不同形态 多态的前提 要有继承或实现关系要有方法的重写要有父类引用指向子类对象 多态的具体实现 对象的多态是多态的核心和重点 规则: 一个对象的编译类型与运行类型可以不一致编译类型在定义对象时,就确定了,…

三数之和问题

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例 1&…