|
| 1 | +--- |
| 2 | +title: 3068.最大节点价值之和:脑筋急转弯+动态规划(O(1)空间) |
| 3 | +date: 2025-05-27 23:50:42 |
| 4 | +tags: [题解, LeetCode, 困难, 位运算, 树, 数组, 动态规划, DP, 排序] |
| 5 | +categories: [题解, LeetCode] |
| 6 | +--- |
| 7 | + |
| 8 | +# 【LetMeFly】3068.最大节点价值之和:脑筋急转弯+动态规划(O(1)空间) |
| 9 | + |
| 10 | +力扣题目链接:[https://leetcode.cn/problems/find-the-maximum-sum-of-node-values/](https://leetcode.cn/problems/find-the-maximum-sum-of-node-values/) |
| 11 | + |
| 12 | +<p>给你一棵 <code>n</code> 个节点的 <strong>无向</strong> 树,节点从 <code>0</code> 到 <code>n - 1</code> 编号。树以长度为 <code>n - 1</code> 下标从 <strong>0</strong> 开始的二维整数数组 <code>edges</code> 的形式给你,其中 <code>edges[i] = [u<sub>i</sub>, v<sub>i</sub>]</code> 表示树中节点 <code>u<sub>i</sub></code> 和 <code>v<sub>i</sub></code> 之间有一条边。同时给你一个 <strong>正</strong> 整数 <code>k</code> 和一个长度为 <code>n</code> 下标从 <strong>0</strong> 开始的 <strong>非负</strong> 整数数组 <code>nums</code> ,其中 <code>nums[i]</code> 表示节点 <code>i</code> 的 <strong>价值</strong> 。</p> |
| 13 | + |
| 14 | +<p>Alice 想 <strong>最大化</strong> 树中所有节点价值之和。为了实现这一目标,Alice 可以执行以下操作 <strong>任意</strong> 次(<strong>包括</strong><strong> 0 次</strong>):</p> |
| 15 | + |
| 16 | +<ul> |
| 17 | + <li>选择连接节点 <code>u</code> 和 <code>v</code> 的边 <code>[u, v]</code> ,并将它们的值更新为: |
| 18 | + |
| 19 | + <ul> |
| 20 | + <li><code>nums[u] = nums[u] XOR k</code></li> |
| 21 | + <li><code>nums[v] = nums[v] XOR k</code></li> |
| 22 | + </ul> |
| 23 | + </li> |
| 24 | +</ul> |
| 25 | + |
| 26 | +<p>请你返回 Alice 通过执行以上操作 <strong>任意次</strong> 后,可以得到所有节点 <strong>价值之和</strong> 的 <strong>最大值</strong> 。</p> |
| 27 | + |
| 28 | +<p> </p> |
| 29 | + |
| 30 | +<p><strong class="example">示例 1:</strong></p> |
| 31 | + |
| 32 | +<p><img alt="" src="https://assets.leetcode.com/uploads/2023/11/09/screenshot-2023-11-10-012513.png" style="width: 300px; height: 277px;padding: 10px; background: #fff; border-radius: .5rem;" /></p> |
| 33 | + |
| 34 | +<pre> |
| 35 | +<b>输入:</b>nums = [1,2,1], k = 3, edges = [[0,1],[0,2]] |
| 36 | +<b>输出:</b>6 |
| 37 | +<b>解释:</b>Alice 可以通过一次操作得到最大价值和 6 : |
| 38 | +- 选择边 [0,2] 。nums[0] 和 nums[2] 都变为:1 XOR 3 = 2 ,数组 nums 变为:[1,2,1] -> [2,2,2] 。 |
| 39 | +所有节点价值之和为 2 + 2 + 2 = 6 。 |
| 40 | +6 是可以得到最大的价值之和。 |
| 41 | +</pre> |
| 42 | + |
| 43 | +<p><strong class="example">示例 2:</strong></p> |
| 44 | + |
| 45 | +<p><img alt="" src="https://assets.leetcode.com/uploads/2024/01/09/screenshot-2024-01-09-220017.png" style="padding: 10px; background: rgb(255, 255, 255); border-radius: 0.5rem; width: 300px; height: 239px;" /></p> |
| 46 | + |
| 47 | +<pre> |
| 48 | +<b>输入:</b>nums = [2,3], k = 7, edges = [[0,1]] |
| 49 | +<b>输出:</b>9 |
| 50 | +<b>解释:</b>Alice 可以通过一次操作得到最大和 9 : |
| 51 | +- 选择边 [0,1] 。nums[0] 变为:2 XOR 7 = 5 ,nums[1] 变为:3 XOR 7 = 4 ,数组 nums 变为:[2,3] -> [5,4] 。 |
| 52 | +所有节点价值之和为 5 + 4 = 9 。 |
| 53 | +9 是可以得到最大的价值之和。 |
| 54 | +</pre> |
| 55 | + |
| 56 | +<p><strong class="example">示例 3:</strong></p> |
| 57 | + |
| 58 | +<p><img alt="" src="https://assets.leetcode.com/uploads/2023/11/09/screenshot-2023-11-10-012641.png" style="width: 600px; height: 233px;padding: 10px; background: #fff; border-radius: .5rem;" /></p> |
| 59 | + |
| 60 | +<pre> |
| 61 | +<b>输入:</b>nums = [7,7,7,7,7,7], k = 3, edges = [[0,1],[0,2],[0,3],[0,4],[0,5]] |
| 62 | +<b>输出:</b>42 |
| 63 | +<b>解释:</b>Alice 不需要执行任何操作,就可以得到最大价值之和 42 。 |
| 64 | +</pre> |
| 65 | + |
| 66 | +<p> </p> |
| 67 | + |
| 68 | +<p><strong>提示:</strong></p> |
| 69 | + |
| 70 | +<ul> |
| 71 | + <li><code>2 <= n == nums.length <= 2 * 10<sup>4</sup></code></li> |
| 72 | + <li><code>1 <= k <= 10<sup>9</sup></code></li> |
| 73 | + <li><code>0 <= nums[i] <= 10<sup>9</sup></code></li> |
| 74 | + <li><code>edges.length == n - 1</code></li> |
| 75 | + <li><code>edges[i].length == 2</code></li> |
| 76 | + <li><code>0 <= edges[i][0], edges[i][1] <= n - 1</code></li> |
| 77 | + <li>输入保证 <code>edges</code> 构成一棵合法的树。</li> |
| 78 | +</ul> |
| 79 | + |
| 80 | +挺有意思的题 |
| 81 | + |
| 82 | +## 解题方法:动态规划 |
| 83 | + |
| 84 | +### 推导一 |
| 85 | + |
| 86 | +前提: |
| 87 | + |
| 88 | +1. 一个数异或$k$两次相当于没异或 |
| 89 | +2. 选择树中一条路径上的所有边,相当于只有路径两端的两个元素异或了$k$(中间每个元素都会异或$k$两次) |
| 90 | +3. 树上任意两点之间存在一条路径 |
| 91 | + |
| 92 | +结论: |
| 93 | + |
| 94 | +1. 相当于我可以从$nums$数组中任选两个数异或,实际上我连边都有哪些都不用管,edges数组直接删! |
| 95 | + |
| 96 | +### 推导二 |
| 97 | + |
| 98 | +前提: |
| 99 | + |
| 100 | +1. 每次操作都会作用两个数 |
| 101 | + |
| 102 | + 1. 如果操作前两个数都异或过,操作后相当于两个数都没异或过 |
| 103 | + 2. 如果操作前两个数都没异或过,操作后相当于两个数都异或过 |
| 104 | + 3. 如果操作前两个数一个异或过一个没异或过,操作后相当于两个数一个没异或过一个异过 |
| 105 | + |
| 106 | +结论: |
| 107 | + |
| 108 | +1. 无论操作多少次,都相当于有偶数个数被异或了 |
| 109 | + |
| 110 | +### 解题思路 |
| 111 | + |
| 112 | +我们可以使用动态规划数组$odd[i]$代表$nums$前$i$个数中有**奇数个**被异或过的元素最大和,$even[i]$代表$nums$前$i$个数中有**偶数个**被异或过的元素最大和。 |
| 113 | + |
| 114 | +对于一个数$nums[i]$,可以选择也可以不选,对应 |
| 115 | + |
| 116 | +$odd[i]=\max(odd[i]+nums[i], even[i]+(nums[i]\verb|^|k))$ |
| 117 | + |
| 118 | +$even[i]=\max(even[i]+nums[i], odd[i]+(nums[i]\verb|^|k))$ |
| 119 | + |
| 120 | +当然也可以原地滚动优化空间。 |
| 121 | + |
| 122 | +### 时空复杂度分析 |
| 123 | + |
| 124 | ++ 时间复杂度$O(len(nums))$ |
| 125 | ++ 空间复杂度$O(1)$ |
| 126 | + |
| 127 | +### AC代码 |
| 128 | + |
| 129 | +#### C++ |
| 130 | + |
| 131 | +```cpp |
| 132 | +/* |
| 133 | + * @Author: LetMeFly |
| 134 | + * @Date: 2025-05-27 23:28:05 |
| 135 | + * @LastEditors: LetMeFly.xyz |
| 136 | + * @LastEditTime: 2025-05-27 23:34:08 |
| 137 | + */ |
| 138 | +typedef long long ll; |
| 139 | + |
| 140 | +class Solution { |
| 141 | +public: |
| 142 | + ll maximumValueSum(vector<int>& nums, int k, vector<vector<int>>& edges) { |
| 143 | + ll odd = LLONG_MIN, even = 0; |
| 144 | + for (int t : nums) { |
| 145 | + ll newO = max(odd + t, even + (t ^ k)); |
| 146 | + ll newE = max(even + t, odd + (t ^ k)); |
| 147 | + odd = newO, even = newE; |
| 148 | + } |
| 149 | + return even; |
| 150 | + } |
| 151 | +}; |
| 152 | +``` |
| 153 | +
|
| 154 | +#### Python |
| 155 | +
|
| 156 | +```python |
| 157 | +''' |
| 158 | +Author: LetMeFly |
| 159 | +Date: 2025-05-27 23:28:05 |
| 160 | +LastEditors: LetMeFly.xyz |
| 161 | +LastEditTime: 2025-05-27 23:40:11 |
| 162 | +''' |
| 163 | +from typing import List |
| 164 | +
|
| 165 | +class Solution: |
| 166 | + def maximumValueSum(self, nums: List[int], k: int, edges: List[List[int]]) -> int: |
| 167 | + odd, even = -100000000000000, 0 |
| 168 | + for t in nums: |
| 169 | + odd, even = max(odd + t, even + (t ^ k)), max(even + t, odd + (t ^ k)) |
| 170 | + return even |
| 171 | +``` |
| 172 | + |
| 173 | +#### Java |
| 174 | + |
| 175 | +```java |
| 176 | +/* |
| 177 | + * @Author: LetMeFly |
| 178 | + * @Date: 2025-05-27 23:28:05 |
| 179 | + * @LastEditors: LetMeFly.xyz |
| 180 | + * @LastEditTime: 2025-05-27 23:45:06 |
| 181 | + */ |
| 182 | +class Solution { |
| 183 | + public long maximumValueSum(int[] nums, int k, int[][] edges) { |
| 184 | + long even = 0, odd = -1000000000000000L; // 记得带“L” |
| 185 | + for (int t : nums) { |
| 186 | + long newO = Math.max(odd + t, even + (t ^ k)); |
| 187 | + long newE = Math.max(even + t, odd + (t ^ k)); |
| 188 | + odd = newO; |
| 189 | + even = newE; |
| 190 | + } |
| 191 | + return even; |
| 192 | + } |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +#### Go |
| 197 | + |
| 198 | +```go |
| 199 | +/* |
| 200 | + * @Author: LetMeFly |
| 201 | + * @Date: 2025-05-27 23:28:05 |
| 202 | + * @LastEditors: LetMeFly.xyz |
| 203 | + * @LastEditTime: 2025-05-27 23:49:20 |
| 204 | + */ |
| 205 | +package main |
| 206 | + |
| 207 | +func maximumValueSum(nums []int, k int, edges [][]int) int64 { |
| 208 | + odd, even := int64(-10000000000000000), int64(0) // -1...0也可能是int |
| 209 | + for _, t := range nums { |
| 210 | + odd, even = max(odd + int64(t), even + int64(t ^ k)), max(even + int64(t), odd + int64(t ^ k)) |
| 211 | + } |
| 212 | + return even |
| 213 | +} |
| 214 | +``` |
| 215 | + |
| 216 | +> 同步发文于[CSDN](https://letmefly.blog.csdn.net/article/details/148267428)和我的[个人博客](https://blog.letmefly.xyz/),原创不易,转载经作者同意后请附上[原文链接](https://blog.letmefly.xyz/2025/05/27/LeetCode%203068.%E6%9C%80%E5%A4%A7%E8%8A%82%E7%82%B9%E4%BB%B7%E5%80%BC%E4%B9%8B%E5%92%8C/)哦~ |
| 217 | +> |
| 218 | +> 千篇源码题解[已开源](https://github.com/LetMeFly666/LeetCode) |
0 commit comments