news 2026/8/22 10:46:02

DeepSeek LeetCode LCP 27. 黑盒光线反射 Java实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DeepSeek LeetCode LCP 27. 黑盒光线反射 Java实现

这道题的核心是预处理 + 有序集合

💡 解题思路

  • 光线路径的循环性:在黑盒中,从任意小孔沿某方向射入的光线,最终都会回到起点并沿相同方向射出,形成一个闭合的“循环”。在所有小孔都关闭的情况下,光线会在循环中无限反射。
  • 状态表示:每一个(小孔编号, 方向)的组合,都代表了光线在循环中的一个“状态”。状态总数是2 * (2*(m+n)) - 4 = 4(m+n)-4
  • 开启小孔的作用:开启一个小孔,相当于在它所属的循环路径上,将该小孔对应的“状态位置”标记为“出口”。
  • 查询的本质open操作就是在当前光线状态所处的循环中,查找该状态位置之后的第一个“出口”
  • 高效数据结构:使用TreeSet维护每个循环上所有已开启小孔的位置,可以高效地完成“查找下一个”和“插入/删除”操作。

☕️ Java 代码实现

importjava.util.*;classBlackBox{privatefinalintn,m;privatefinalinttotalHoles;// 小孔总数: 2 * (m + n)privatefinalinttotalStates;// 状态总数: 4 * (m + n) - 4// cycleId[hole][dirIndex]: 记录状态 (hole, direction) 所属的循环ID// posInCycle[hole][dirIndex]: 记录状态在所属循环中的位置privatefinalint[][]cycleId;privatefinalint[][]posInCycle;// 每个循环对应一个 TreeSet,存储该循环上所有已开启小孔的位置privatefinalList<TreeSet<Integer>>cycles;privateintcycleCount;publicBlackBox(intn,intm){this.n=n;this.m=m;this.totalHoles=2*(m+n);this.totalStates=4*(m+n)-4;this.cycleId=newint[totalHoles][2];this.posInCycle=newint[totalHoles][2];// 初始化:-1 表示该状态尚未被分配循环for(inti=0;i<totalHoles;i++){Arrays.fill(cycleId[i],-1);Arrays.fill(posInCycle[i],-1);}this.cycles=newArrayList<>();this.cycleCount=0;// 在构造函数中预处理所有循环findAllCycles();}// 预处理所有光线的循环路径privatevoidfindAllCycles(){for(intstartHole=0;startHole<totalHoles;startHole++){for(intstartDir:newint[]{1,-1}){// 拐角处的小孔只有一个合法方向if(isCorner(startHole)&&!isValidCornerDirection(startHole,startDir)){continue;}intdirIdx=dirToIndex(startDir);// 如果这个状态已经属于某个循环,则跳过if(cycleId[startHole][dirIdx]!=-1){continue;}// 发现一个新循环TreeSet<Integer>cycleSet=newTreeSet<>();intcurrentCycleId=cycleCount++;cycles.add(cycleSet);intcurHole=startHole;intcurDir=startDir;intpos=0;// 沿着光线路径前进,直到回到起点状态do{// 记录当前状态cycleId[curHole][dirToIndex(curDir)]=currentCycleId;posInCycle[curHole][dirToIndex(curDir)]=pos;// 计算下一个状态: 经过小孔 curHole 后,方向取反intnextDir=-curDir;// 计算从 (curHole, nextDir) 出发到达的下一个小孔编号intnextHole=getNextHole(curHole,nextDir);curHole=nextHole;curDir=nextDir;pos++;}while(!(curHole==startHole&&curDir==startDir));}}}// 判断小孔是否为四个拐角privatebooleanisCorner(inthole){returnhole==0||hole==m||hole==m+n||hole==2*m+n;}// 判断拐角处的方向是否合法privatebooleanisValidCornerDirection(inthole,intdir){if(hole==0)returndir==-1;if(hole==m)returndir==1;if(hole==m+n)returndir==-1;if(hole==2*m+n)returndir==1;returntrue;}// 将方向 (1 或 -1) 映射到数组索引 (0 或 1)privateintdirToIndex(intdir){returndir==1?0:1;}// 核心函数:给定当前小孔和下一步方向,计算到达的下一个小孔编号privateintgetNextHole(inthole,intdir){intnext;if(dir==1){// 沿 y=x 方向// 关于 (m+n) 对称next=2*(m+n)-hole;}else{// dir == -1, 沿 y=-x 方向if(hole<=2*m){next=2*m-hole;}else{next=2*(2*m+n)-hole;}}// 取模运算,确保结果在 [0, totalHoles) 范围内return(next%totalHoles+totalHoles)%totalHoles;}// 开启小孔并射入光线,返回射出的小孔编号publicintopen(intindex,intdirection){// 1. 打开小孔:将小孔在两个方向上的状态位置,都加入到对应的循环中for(intd:newint[]{1,-1}){intcycleIdx=cycleId[index][dirToIndex(d)];if(cycleIdx!=-1){intpos=posInCycle[index][dirToIndex(d)];cycles.get(cycleIdx).add(pos);}}// 2. 执行查询,返回光线射出的小孔returnshoot(index,direction);}// 关闭小孔publicvoidclose(intindex){// 从小孔所属的所有循环中移除其位置记录for(intd:newint[]{1,-1}){intcycleIdx=cycleId[index][dirToIndex(d)];if(cycleIdx!=-1){intpos=posInCycle[index][dirToIndex(d)];cycles.get(cycleIdx).remove(pos);}}}// 内部查询方法:从 (index, direction) 状态出发,寻找下一个开启的小孔privateintshoot(intindex,intdirection){intdirIdx=dirToIndex(direction);intcycleIdx=cycleId[index][dirIdx];intcurPos=posInCycle[index][dirIdx];TreeSet<Integer>cycleSet=cycles.get(cycleIdx);// 在循环中寻找位置大于 curPos 的开启小孔IntegernextPos=cycleSet.higher(curPos);if(nextPos==null){// 如果没有更大的,则取循环中最小的开启小孔(绕一圈)nextPos=cycleSet.first();}// 根据位置找到对应的小孔编号returnfindHoleByPos(cycleIdx,nextPos);}// 根据循环ID和位置找到对应的小孔编号privateintfindHoleByPos(intcycleIdx,intpos){for(inthole=0;hole<totalHoles;hole++){for(intd:newint[]{1,-1}){if(cycleId[hole][dirToIndex(d)]==cycleIdx&&posInCycle[hole][dirToIndex(d)]==pos){returnhole;}}}return-1;// 正常情况下不会发生}}

🔑 关键点说明

  • findAllCycles():这是预处理的核心。它遍历所有可能的起始状态(startHole, startDir),模拟光路直到回到起点,从而发现一个完整的循环。
  • getNextHole():这个函数是模拟光路的基础。它利用黑盒的几何对称性,通过数学计算直接得出光线经过反射后到达的下一个小孔编号。
  • TreeSet:这是实现高效查询的关键。它维护了每个循环上当前所有开启小孔的位置(pos)。
    • add(pos)/remove(pos):用于openclose操作。
    • higher(curPos):用于shoot,可以O(log N)地找到当前位置之后的下一个出口。
  • 二维数组记录状态cycleIdposInCycle两个二维数组,以[hole][dirIndex]为索引,记录了每个状态所属的循环和位置,是后续所有操作的基础。

⏱️ 复杂度分析

  • 时间复杂度
    • 预处理 (findAllCycles):需要遍历所有O(totalStates)个状态,因此时间复杂度为O(n + m)
    • open/close/shoot:主要操作是TreeSetaddremovehigher,时间复杂度均为O(log K),其中 K 是该循环上已开启的小孔数量。
  • 空间复杂度:需要存储所有状态的信息和TreeSet,整体空间复杂度为O(n + m)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 10:45:47

AI写作风险警示:从技术视角看版权、幻觉与伦理陷阱

这次我们来看一个关于AI写作的警示性案例。项目标题“Scalzi – Another Reason Not to Use ‘AI’ for Your Writing”并非一个技术工具&#xff0c;而是一篇由知名科幻作家约翰斯卡尔齐&#xff08;John Scalzi&#xff09;撰写的博客文章。这篇文章的核心观点是&#xff0c;…

作者头像 李华
网站建设 2026/8/22 10:43:19

工程技能基准样例审计工具:从输入校验到离线报告的完整实现

项目编号&#xff1a;20260821-010。本文代码、测试、文档、示例数据和效果图均为独立编写&#xff0c;不包含热点产品或开源项目源码、品牌素材与官方截图。 问题与目标 检查基准任务的输入版本、期望结果、误差阈值、隔离条件和复跑证据。在真实工程里&#xff0c;这类工作最…

作者头像 李华
网站建设 2026/8/22 10:43:10

生鲜供应链联合决策模型:定价与补货协同优化实战

1. 这不是一篇“论文模板”&#xff0c;而是一套可复用的生鲜供应链决策骨架高教社杯数模竞赛里&#xff0c;C题向来是实操性最强的赛道——它不考你推导拉格朗日乘子&#xff0c;也不让你手撕偏微分方程&#xff0c;而是把你直接扔进一个真实的超市生鲜部&#xff1a;凌晨三点…

作者头像 李华
网站建设 2026/8/22 10:42:36

多智能体强化学习如何驱动EDA工具实现自主进化与优化

1. 项目概述&#xff1a;当EDA工具学会“自我进化”最近在电子设计自动化&#xff08;EDA&#xff09;圈子里&#xff0c;一个概念正在被越来越多地讨论&#xff1a;如果我们的设计工具不再是被动执行的软件&#xff0c;而是能像一群有经验的工程师一样&#xff0c;自主协作、发…

作者头像 李华
网站建设 2026/8/22 10:40:50

华为eNSP安装全攻略:解决依赖冲突,一次成功运行网络实验

如果你正准备学习华为网络技术&#xff0c;或者正在备考HCIA/HCIP/HCIE认证&#xff0c;那么eNSP&#xff08;Enterprise Network Simulation Platform&#xff09;这个软件你一定绕不开。但现实情况是&#xff0c;很多新手在安装eNSP的第一步就卡住了——不是WinPcap报错&…

作者头像 李华
网站建设 2026/8/22 10:40:22

基于Dify与RAG技术,从零构建本地AI智能体实战指南

之前想为特定游戏&#xff08;比如三角洲&#xff09;构建一个专属的AI助手&#xff0c;能回答游戏攻略、角色技能、装备搭配等复杂问题&#xff0c;但发现从零开发一个集成了知识库和智能体工作流的系统门槛极高。直到遇到了 Dify&#xff0c;它通过可视化的拖拽操作&#xff…

作者头像 李华