news 2026/9/18 13:49:26

LeetCode 1261 在受污染的二叉树中查找元素:还原树与「target + 1」二进制寻路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 1261 在受污染的二叉树中查找元素:还原树与「target + 1」二进制寻路

LeetCode 1261 在受污染的二叉树中查找元素:还原树与「target + 1」二进制寻路

【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode

导读

本题(LeetCode 1261,Find Elements in a Contaminated Binary Tree)要求在一棵节点值全部被污染为-1的二叉树上先按给定递推规则还原节点值,再设计一个支持快速判断目标值是否存在的FindElements类。本文以 problems/1261.find-elements-in-a-contaminated-binary-tree.md 为主体,完整覆盖暴力递归、空间换时间(HashSet)与二进制寻路三种解法,并结合二叉树与二进制数位的关系推导出 O(1) 额外空间的查找方案。读完本文,你将掌握「用值加 1 后的二进制位在树上寻路」这一技巧,并能直接应用到 1104 二叉树寻路 等同类题型。


题目描述与规则

给出一个满足下述规则的二叉树:

  • root.val == 0
  • 如果treeNode.val == xtreeNode.left != null,那么treeNode.left.val == 2 * x + 1
  • 如果treeNode.val == xtreeNode.right != null,那么treeNode.right.val == 2 * x + 2

现在这个二叉树受到「污染」,所有的treeNode.val都变成了-1。需要先还原二叉树,然后实现FindElements类:

  • FindElements(TreeNode* root):用受污染的二叉树初始化对象,先把它还原;
  • bool find(int target):判断目标值target是否存在于还原后的二叉树中并返回结果。

示例

示例 1:

输入: ["FindElements","find","find"] [[[-1,null,-1]],[1],[2]] 输出: [null,false,true] 解释: FindElements findElements = new FindElements([-1,null,-1]); findElements.find(1); // return False findElements.find(2); // return True

示例 2:

输入: ["FindElements","find","find","find"] [[[-1,-1,-1,-1,-1]],[1],[3],[5]] 输出: [null,true,true,false] 解释: FindElements findElements = new FindElements([-1,-1,-1,-1,-1]); findElements.find(1); // return True findElements.find(3); // return True findElements.find(5); // return False

示例 3:

输入: ["FindElements","find","find","find","find"] [[[-1,null,-1,-1,null,-1]],[2],[3],[4],[5]] 输出: [null,true,false,false,true] 解释: FindElements findElements = new FindElements([-1,null,-1,-1,null,-1]); findElements.find(2); // return True findElements.find(3); // return False findElements.find(4); // return False findElements.find(5); // return True

数据范围提示

  • TreeNode.val == -1(所有节点均被污染)
  • 二叉树的高度不超过 20
  • 节点总数在[1, 10^4]之间
  • 调用find()的总次数在[1, 10^4]之间
  • 0 <= target <= 10^6

前置知识

  • 二进制:理解target + 1的二进制表示如何编码了从根节点到目标节点的一条路径,是二进制寻路法的核心前提。
  • 满二叉树/堆式索引的性质:若父节点值为x,左子节点为2x + 1、右子节点为2x + 2,这本质上是堆(Heap)的 0-based 索引规律,与 1104 二叉树寻路 中利用的满二叉树性质同源。

解法一:暴力法(递归还原 + 递归查找)

思路

最直接的想法是:递归还原整棵树,把所有-1按规则改写为正确值;find时再递归遍历整棵树查找目标值。代码非常简单,但会超时(详见下文复杂度分析)。

代码

# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class FindElements: node = None def __init__(self, root: TreeNode): def recover(node): if not node: return node; if node.left: node.left.val = 2 * node.val + 1 if node.right: node.right.val = 2 * node.val + 2 recover(node.left) recover(node.right) return node root.val = 0 self.node = recover(root) def find(self, target: int) -> bool: def findInTree(node, target): if not node: return False if node.val == target: return True return findInTree(node.left, target) or findInTree(node.right, target) return findInTree(self.node, target) # Your FindElements object will be instantiated and called as such: # obj = FindElements(root) # param_1 = obj.find(target)

(对应原文档 problems/1261.find-elements-in-a-contaminated-binary-tree.md)

复杂度与超时原因

  • 还原:每个节点访问一次,时间复杂度 O(N),其中 N 为节点数(最多 10^4)。
  • 单次find:最坏情况要遍历整棵树,时间复杂度 O(N)。
  • 总复杂度:find总调用次数最多也是 10^4,最坏总代价 O(N × M) ≈ 10^4 × 10^4 = 10^8,在时间限制下很可能超时。

因此需要优化。原文档明确指出:"上述代码会超时,我们来考虑优化"


解法二:空间换时间(HashSet)

思路

既然瓶颈在find的线性遍历,就用空间换时间:还原树的同时,把所有节点值存入一个集合(set)。此后find只需一次哈希查找,时间复杂度 O(1)。

需要注意一个实现细节(原文档特别强调):self.seen = set()不能放在__init__方法外侧定义为类属性。因为多个测试用例之间不会销毁FindElements的实例变量,若把集合定义为类级共享变量,会残留上一个用例的数据,导致错误结果。

代码

# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class FindElements: def __init__(self, root: TreeNode): # set 不能放在init外侧。 因为测试用例之间不会销毁FindElements的变量 self.seen = set() def recover(node): if not node: return node; if node.left: node.left.val = 2 * node.val + 1 self.seen.add(node.left.val) if node.right: node.right.val = 2 * node.val + 2 self.seen.add(node.right.val) recover(node.left) recover(node.right) return node root.val = 0 self.seen.add(0) self.node = recover(root) def find(self, target: int) -> bool: return target in self.seen # Your FindElements object will be instantiated and called as such: # obj = FindElements(root) # param_1 = obj.find(target)

(对应原文档 problems/1261.find-elements-in-a-contaminated-binary-tree.md)

复杂度分析

  • 还原:O(N)(遍历每个节点并写入set)。
  • find:O(1)(哈希查找)。
  • 空间:O(N),需要一个set保存全部节点值。原文档指出:这种解法可以 AC,但在数据量非常大时,可能 MLE(内存超限),因此继续优化。

解法三:二进制法(target + 1 寻路,O(1) 空间)

思路:如果先把所有数加 1 会怎么样?

这是一条非常巧妙的思路。观察还原后的二叉树节点值,若把每个值都加 1,则:

  • 根节点:0 + 1 = 1
  • 左子节点:(2x + 1) + 1 = 2(x + 1),即父节点加 1 后左移一位,末位补 0;
  • 右子节点:(2x + 2) + 1 = 2(x + 1) + 1,即父节点加 1 后左移一位,末位补 1。

换句话说,加 1 之后,这棵树变成了标准的1-based 堆索引结构:子节点索引 = 父节点索引 × 2(左)或 × 2 + 1(右)。于是「值加 1」后的二进制表示恰好编码了从根到该节点的路径:

  • 二进制最高位的1对应根节点;
  • 其后的每一位:0表示往左走,1表示往右走。

举例:target = 9

target + 1 = 10,二进制表示为1010。忽略最高位1,剩余路径位为010

  • 0向左(根 → 左子)
  • 1向右(左子 → 右子)
  • 0向左(右子 → 左子)

按这条路径走,即可到达值9的节点。同理可验证:target = 22 + 1 = 3(二进制11),路径位为1,即从根向右一步到达值为2的节点,与题目示例 1 中find(2) == True一致。

代码

# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class FindElements: node = None def __init__(self, root: TreeNode): def recover(node): if not node: return node; if node.left: node.left.val = 2 * node.val + 1 if node.right: node.right.val = 2 * node.val + 2 recover(node.left) recover(node.right) return node root.val = 0 self.node = recover(root) def find(self, target: int) -> bool: node = self.node for bit in bin(target+1)[3:]: node = node and (node.left, node.right)[int(bit)] return bool(node) # Your FindElements object will be instantiated and called as such: # obj = FindElements(root) # param_1 = obj.find(target)

(对应原文档 problems/1261.find-elements-in-a-contaminated-binary-tree.md)

代码逐行拆解

  • bin(target + 1):得到类似0b1010的字符串。
  • [3:]:跳过前三个字符0b1,即同时去掉二进制前缀0b与最高位的1(最高位对应根节点),剩余的每一位就是从根出发的寻路指令。
  • (node.left, node.right)[int(bit)]bit'0'时取node.left,为'1'时取node.right,相当于用下标索引替代了if/else
  • node = node and ...:利用短路求值——一旦nodeNone(路径上某一步该方向不存在子节点),后续表达式不再计算并保持Nonefind返回False
  • 最终bool(node):路径走完仍为非空节点,说明目标值存在,返回True

复杂度分析

  • 还原:O(N)(与暴力法相同的递归还原过程)。
  • find:O(log target)(树高不超过 20,target <= 10^6,二进制位数有限,即沿树深度方向行走),远快于暴力法的 O(N)。
  • 空间:O(1),除了递归还原时隐式的调用栈外,find不需要任何额外数据结构,彻底规避了 HashSet 方案的 MLE 风险

三种解法对比与选型建议

解法还原复杂度find 复杂度额外空间结论
暴力法(递归遍历查找)O(N)O(N)O(树高)总代价可能达 10^8,会超时
空间换时间(HashSet)O(N)O(1)O(N)可以 AC,但大数据量下有 MLE 风险
二进制法(target + 1 寻路)O(N)O(log target)O(1)时间、空间均衡,最优雅

工程上推荐:

  • 若追求find极致速度且内存充裕,选 HashSet 方案;
  • 若追求空间极致(或数据规模极大),选二进制寻路方案,且它无需额外建集合,find的 O(log target) 在树高 ≤ 20 的限制下几乎可视为常数。

关键点解析

  1. 空间换时间:以 O(N) 的集合存储换取find的 O(1) 查询,是"还原 + 多次查询"类题目最常见的优化方向。
  2. 二进制思维:将节点值整体加 1 后,树退化为 1-based 堆索引结构,target + 1的二进制位直接编码了根到目标节点的路径——0向左、1向右。
  3. 将 target + 1:这一步是整个二进制法的题眼,务必理解"加 1"如何把2x+1 / 2x+2的递推关系对齐到"左移 + 0/1 补位"的二进制规律上。
  4. 实例变量与类变量的坑set必须放在__init__内,避免测试用例间数据残留(原文档明确强调)。

仓库内的学习路径与延伸

该题被收录于本仓库的多种索引中,便于按难度与专题检索:

  • collections/medium.md:归入中等难度(Medium)题单;
  • SUMMARY.md 与 introduction.md:分别位于目录与简介的题解索引中;
  • README.md:主 README 的题解目录同样收录本题。

与本题二进制思想强相关的姊妹题是 1104 二叉树寻路(Path In Zigzag Labelled Binary Tree):它同样利用「值加 1 / 索引」与满二叉树层级(第 k 层最小值2^(level-1)、最大值2^level - 1)的性质,逆向求出根到节点的路径。将两题对照学习,可以系统掌握"二叉树索引与二进制位"这一通用套路。此外,二进制与位运算的系统性专题可参考 thinkings/bit.md,二叉树的遍历与结构基础可参考 thinkings/binary-tree-traversal.md。


小结

本题的核心递推left = 2x + 1right = 2x + 2与堆索引完全同构,因此"值 + 1 的二进制位 = 路径"这一性质是天然成立的。从暴力递归 → HashSet 空间换时间 → 二进制寻路,三种解法展示了同一道题在不同约束(时间、内存)下的渐进式优化思路。掌握bin(target + 1)[3:]的写法与node and (node.left, node.right)[int(bit)]的短路技巧,你就能在 O(1) 额外空间内完成 O(log target) 的快速查找。

【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 13:48:43

从 “歪瓜裂枣“ 到粒粒圆润:一粒刀豆的膨化史

刀豆是典型的药食同源品种 —— 嫩荚能当菜&#xff0c;老籽能入药&#xff0c;《本草纲目》说它 "温中下气&#xff0c;利肠胃&#xff0c;止呃逆&#xff0c;益肾补元"。但真要把它做成一款 "安全、好吸收、好吃" 的现代产品&#xff0c;远没有把豆子丢进…

作者头像 李华
网站建设 2026/9/18 13:48:36

RL强化学习从小白到老鸟(二)——手撕GPT(零基础保姆级教学)

标题RL强化学习从小白到老鸟(二)——手撕GPT&#xff08;零基础保姆级教学&#xff09; 第三篇&#xff1a;让贪吃蛇训练更稳定、更容易复现 简介 帮老师带几个研究生&#xff0c;他们想学GPT和强化学习&#xff0c;正好我两者都略懂&#xff0c;正在研究结合两者优势创建一个…

作者头像 李华
网站建设 2026/9/18 13:48:32

gsplat:工业级高斯泼溅的CUDA原生实现与部署指南

1. 项目概述&#xff1a;为什么是 gsplat&#xff0c;而不是其他高斯泼溅实现&#xff1f;最近三个月&#xff0c;我在三个不同客户现场部署3D重建管线时&#xff0c;反复被问到一个问题&#xff1a;“你们用的是哪个高斯泼溅实现&#xff1f;原生3DGS太吃显存&#xff0c;训练…

作者头像 李华
网站建设 2026/9/18 13:47:42

注意避坑!不是所有 AI 写作工具都靠谱,2026 导师推荐工具盘点

每年毕业季&#xff0c;无数同学深陷论文难题&#xff1a;开题毫无思路、搭建框架耗费数日、初稿逻辑松散、查重标红泛滥、AI检测超标、格式反复被导师驳回。现如今市面上通用型AI工具遍地开花&#xff0c;但绝大多数通用大模型存在编造虚假参考文献、学术语句口语化、AI生成痕…

作者头像 李华
网站建设 2026/9/18 13:46:27

Manus:基于多智能体的可执行AI Agent工作流

简介&#xff1a;本资源是面向AI开发者、数据分析师与商业智能从业者的《2025 Manus学习手册》&#xff0c;系统讲解中国Monica团队研发的通用AI智能体平台Manus的核心理念、多智能体架构&#xff08;MAS&#xff09;原理及全流程自动化能力。手册深入解析Manus如何自主理解任务…

作者头像 李华
网站建设 2026/9/18 13:43:19

LangChain:构建稳定AI应用的技术架构与实践

1. LangChain在AI浪潮中的定位思考第一次接触LangChain是在2022年底&#xff0c;当时我正在为一个跨国电商客户构建多语言客服系统。传统方案需要为每种语言维护独立的意图识别和对话管理模块&#xff0c;开发团队疲于应对各种边缘case。当看到LangChain通过组合LLM与其他工具实…

作者头像 李华