当前位置: 首页 > news >正文

千问 LeetCode 2920. 收集所有金币可获得的最大积分 Java实现

这是一道非常经典的树形动态规划(Tree DP)结合记忆化搜索的题目。

💡 核心解题思路

1. 题目分析:
* 这是一棵无向树,我们需要从根节点(0)出发收集所有金币。
* 每个节点有两种收集方式:
* 方式1:获得 coins[i] - k 积分。
* 方式2:获得 coins[i] / 2 积分,但代价是当前节点子树中所有节点的金币都会减半。
2. 关键洞察(状态压缩):
* 题目中金币的最大值 coins[i] 不超过 10000。
* 一个数连续除以 2(右移),最多只需要 14 次就会变成 0(因为 2^{14} = 16384 > 10000)。
* 因此,我们不需要真正去修改子树中每个节点的金币数值。我们只需要用一个状态 j 来记录从根节点到当前节点的路径上,一共执行了多少次“方式2”的操作。
3. 状态定义:
* 定义 dfs(i, j, father) 表示:当前在节点 i,其祖先节点(不包括自己)一共执行了 j 次“方式2”操作,且父节点为 father 时,以 i 为根的子树能获得的最大积分。
4. 状态转移:
* 当前节点 i 的实际金币数为 coins[i] >> j(即 coins[i] / 2^j)。
* 选择方式1:当前获得 (coins[i] >> j) - k,子节点的状态依然是 j。
* 选择方式2:当前获得 (coins[i] >> j) >> 1(即 coins[i] >> (j+1)),子节点的状态变为 j + 1。
* 取两者的最大值即可。

💻 Java 代码实现

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
public int maximumPoints(int[][] edges, int[] coins, int k) {
int n = coins.length;
// 1. 构建邻接表表示树
List<Integer>[] graph = new ArrayList[n];
Arrays.setAll(graph, i -> new ArrayList<>());
for (int[] edge : edges) {
int x = edge[0];
int y = edge[1];
graph[x].add(y);
graph[y].add(x);
}

// 2. 初始化记忆化数组 memo[i][j]
// 表示节点 i 在祖先进行了 j 次减半操作下的最大积分
// j 最大取 14 即可,因为 10000 >> 14 = 0
int[][] memo = new int[n][14];
for (int[] row : memo) {
Arrays.fill(row, -1); // -1 表示未计算过
}

// 3. 从根节点 0 开始 DFS,初始父节点为 -1,初始减半次数为 0
return dfs(0, 0, -1, memo, graph, coins, k);
}

private int dfs(int i, int j, int father, int[][] memo, List<Integer>[] graph, int[] coins, int k) {
// 如果该状态已经计算过,直接返回
if (memo[i][j] != -1) {
return memo[i][j];
}

// 当前节点 i 在经过 j 次减半后的实际金币数
int currentCoin = coins[i] >> j;

// 策略1:使用方式1收集 (coins[i] >> j) - k
// 子节点的减半次数依然保持为 j
int res1 = currentCoin - k;

// 策略2:使用方式2收集 (coins[i] >> j) / 2
// 子节点的减半次数变为 j + 1
int res2 = currentCoin >> 1;

// 遍历所有子节点(排除父节点)
for (int child : graph[i]) {
if (child == father) {
continue;
}
// 累加策略1下,子树的收益
res1 += dfs(child, j, i, memo, graph, coins, k);

// 累加策略2下,子树的收益
// 剪枝:如果 j >= 13,说明再右移一次金币肯定为0,没必要继续深入递归计算 res2
if (j < 13) {
res2 += dfs(child, j + 1, i, memo, graph, coins, k);
}
}

// 记录并返回最大值
memo[i][j] = Math.max(res1, res2);
return memo[i][j];
}
}

📊 复杂度分析

* 时间复杂度:O(N)。其中 N 是节点数量。虽然看起来有很多状态,但由于金币数最多减半 14 次就会变成 0,状态数被限制在 N times 14 以内。每个状态只会被计算一次,因此整体接近线性时间复杂度。
* 空间复杂度:O(N)。主要用于存储邻接表 graph 和记忆化数组 memo,以及递归调用的栈空间。

⚠️ 易错点提示
* 避免死循环:树是无向图转换来的,DFS 时一定要传入 father 参数,防止在父子节点之间来回遍历。
* 右移边界:当 j 很大时(比如超过 13),coins[i] >> j 已经等于 0 了。此时继续递归 j+1 没有意义,可以直接剪枝,避免不必要的计算。

http://www.cnnetsun.cn/news/2701600.html

相关文章:

  • AtlasOS终极指南:如何通过开源方案彻底优化Windows系统性能
  • STM32F103C8T6继电器控制KEIL工程:PB6驱动+LED状态指示+硬件接线图
  • LongCat-Flash-Lite-FP8安全与部署注意事项:MIT许可证详解与使用限制
  • Sora 2色彩空间配置全解密(行业首份LUT链兼容性白皮书)
  • HiDream-I1高级应用:自定义prompt文件与批量图像生成技巧
  • SSC工具生成的MyApplication.xml文件,到底怎么用?一份给TwinCAT工程师的配置详解
  • SilentPatch:让经典GTA游戏在现代系统上完美运行的终极解决方案
  • 如何通过HsMod打造终极炉石传说游戏体验:55项功能完整指南
  • 如何完全掌控你的微信聊天记录:WeChatMsg本地备份工具终极指南
  • 金属波纹管厂家生产与镀锌产品最新价格一览
  • YOLOv5模型瘦身实战:用GSConv+Slim-Neck替换Neck模块,推理速度提升20%
  • 第一次看懂 SQL 注入利用流程:从判断字段数到获取数据库信息
  • D43: 项目验收文档自动化
  • 拆解Geant4模拟内核:Run、Event、Step、Track到底怎么工作?给初学者的可视化解读
  • AI 内容泛滥时代,技术驱动型品牌如何构建可信的 “活人感“ 运营体系
  • Windows 11 LTSC系统安装微软商店的终极指南:3步告别应用荒
  • ArcGIS JS 态势标绘教程:扇形(Sector)
  • 大卷积核的‘文艺复兴’:从RepLKNet到UniRepLKNet,我们该如何设计下一个通用视觉主干网络?
  • 手把手教你用带参数的FC写一个‘万能’星三角启动程序(附TIA Portal V18程序截图)
  • SonarQube 里给 AI 代码做扫描
  • 别再问红外图像为啥时黑时彩了!一文搞懂红外成像原理与伪彩色增强(附Python代码示例)
  • PyTorch三模型面部表情识别实战包:CNN/VGG/ResNet一键运行,含人脸检测、预训练权重与演示图
  • 基于OpenCode的Harness架构实战v2.2(windows系统)
  • STS-Bcut语音转字幕终极指南:3步实现视频自动字幕生成
  • Linux tar打包压缩全参数详解——打包、压缩、解压、查看、排除文件完整实战
  • 智慧工厂里的视觉技术革命(19)
  • UE5 GAS实战:用Meta Attributes和Set by Caller,让你的RPG伤害计算告别混乱
  • Gitlab安装与配置
  • 从CT原始DICOM到4K手术教学动画:Sora 2端到端工作流仅需22分钟——华西医院介入科实测全链路拆解
  • Windows下MMDetection从安装到跑通第一个目标检测Demo(含权重文件下载与路径配置)