【LeetCode热题100】Java详解:二叉树的最近公共祖先(含递归/父指针双解法与工程实践)

【LeetCode热题100】Java详解:二叉树的最近公共祖先(含递归/父指针双解法与工程实践)

面向人群

  • 正在准备技术面试(尤其是大厂后端、算法岗)的开发者
  • 已掌握二叉树基本遍历,希望深入理解LCA(最近公共祖先)算法的学习者
  • 刷 LeetCode「热题100」或「二叉树专题」的中级程序员
  • 对递归思想、树形DP感兴趣的工程人员

📌 本文适合已能手写二叉树DFS遍历的读者。若对递归、哈希表不熟悉,建议先完成 LeetCode 第235题(二叉搜索树的最近公共祖先)和第104题(二叉树的最大深度)。

关键词

LeetCode 236二叉树的最近公共祖先LCA递归父指针深度优先搜索树形DP面试高频题时间复杂度分析

阅读前必须掌握的基础

在深入阅读本文前,请确保你已熟练掌握以下知识点:

  1. 二叉树的基本操作

    • DFS遍历(前序、中序、后序)
    • 递归实现树遍历
    • 树节点的定义和基本操作
  2. 递归思想

    • 递归终止条件
    • 递归返回值的含义
    • 自底向上的递归处理
  3. Java 基础数据结构

    • HashMap的基本操作(put,get
    • HashSet的基本操作(add,contains
    • 递归栈的工作原理
  4. 复杂度分析

    • 能分析 O(n) 时间复杂度
    • 理解递归栈空间复杂度

✅ 推荐前置练习:

  • LeetCode 235:二叉搜索树的最近公共祖先
  • LeetCode 104:二叉树的最大深度
  • LeetCode 112:路径总和

一、原题回顾

题目链接:236. 二叉树的最近公共祖先

题目描述

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科定义:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

示例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输出:3 解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

可视化树结构:

3 / \ 5 1 / \ / \ 6 2 0 8 / \ 7 4
示例 2:
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 输出:5 解释:节点 5 和节点 4 的最近公共祖先是节点 5 。因为根据定义最近公共祖先节点可以为节点本身。
示例 3:
输入:root = [1,2], p = 1, q = 2 输出:1
提示:
  • 树中节点数目在范围[2, 10⁵]
  • -10⁹ <= Node.val <= 10⁹
  • 所有Node.val互不相同
  • p != q
  • pq均存在于给定的二叉树中

二、原题分析

2.1 核心概念理解

最近公共祖先(LCA)的关键特性

  1. 祖先关系:LCA必须是p和q的共同祖先
  2. 深度最大:在所有公共祖先中,LCA的深度最大(离叶子最近)
  3. 自包含性:如果p是q的祖先,那么p就是LCA

2.2 问题的难点

  • 普通二叉树:没有BST那样的有序性质,无法通过值的大小判断方向
  • 任意位置:p和q可以在树的任意位置,包括同一子树或不同子树
  • 自包含情况:需要正确处理一个节点是另一个节点祖先的情况

2.3 解题思路的核心洞察

对于任意节点x,要成为p和q的LCA,必须满足以下两种情况之一

情况1:p和q分别在x的左右子树中

  • 左子树包含p,右子树包含q(或反之)
  • 此时x就是LCA

情况2:x本身就是p或q,且另一个节点在其子树中

  • x = p,且q在x的子树中
  • x = q,且p在x的子树中
  • 此时x就是LCA

✅ 这个洞察是递归解法的基础!


三、答案构思:两种主流解法

本题有两种经典解法,体现了递归思维 vs 数据结构思维的不同角度:

方法核心思想时间复杂度空间复杂度实现难度
方法一:递归(推荐)自底向上,后序遍历,树形DP思想O(N)O(N)中等
方法二:父指针 + 哈希表存储父节点信息,模拟向上查找O(N)O(N)简单

💡 两种方法时间复杂度相同,但递归法更优雅,父指针法更直观。


四、完整答案与代码实现(Java)

方法一:递归(后序遍历 + 树形DP)

思路详解

这是最经典的解法,采用后序遍历的思想:

  1. 递归函数定义dfs(root, p, q)返回布尔值,表示以root为根的子树是否包含p或q
  2. 递归过程
    • 先递归处理左子树,得到lson
    • 再递归处理右子树,得到rson
    • 检查当前节点是否满足LCA条件
  3. LCA判断条件
    • (lson && rson):p和q分别在左右子树
    • ((root == p || root == q) && (lson || rson)):当前节点是p或q,且另一个在子树中

🔑关键技巧:使用全局变量ans记录找到的LCA,一旦找到就不再更新(因为后序遍历保证第一次找到的就是深度最大的)

Java 代码(标准版)
classSolution{privateTreeNodeans;publicTreeNodelowestCommonAncestor(TreeNoderoot,TreeNodep,TreeNodeq){dfs(root,p,q);returnans;}/** * DFS遍历,返回以root为根的子树是否包含p或q */privatebooleandfs(TreeNoderoot,TreeNodep,TreeNodeq){if(root==null){returnfalse;}// 递归处理左右子树booleanlson=dfs(root.left,p,q);booleanrson=dfs(root.right,p,q);// 检查当前节点是否为LCAif((lson&&rson)||((root==p||root==q)&&(lson||rson))){ans=root;}// 返回当前子树是否包含p或qreturnlson||rson||(root==p||root==q);}}
Java 代码(优化版 - 直接返回LCA)

有些面试官可能不喜欢使用全局变量,我们可以直接让递归函数返回LCA:

classSolution{publicTreeNodelowestCommonAncestor(TreeNoderoot,TreeNodep,TreeNodeq){// 终止条件if(root==null||root==p||root==q){returnroot;}// 递归处理左右子树TreeNodeleft=lowestCommonAncestor(root.left,p,q);TreeNoderight=lowestCommonAncestor(root.right,p,q);// 情况1:p和q分别在左右子树if(left!=null&&right!=null){returnroot;}// 情况2:p和q在同一子树,返回非空的那个returnleft!=null?left:right;}}

优化版的优势

  • 无需全局变量,更符合函数式编程思想
  • 代码更简洁,逻辑更清晰
  • 同样保证O(N)时间复杂度
执行过程图解(示例1)

p=5, q=1为例:

后序遍历顺序:6→7→4→2→5→0→8→1→3 访问6: 不是5或1,返回false 访问7: 不是5或1,返回false 访问4: 不是5或1,返回false 访问2: 左=false, 右=false, 自己不是5/1 → 返回false 访问5: 自己是5 → 返回true (此时lson=false, rson=false) 访问0: 不是5或1,返回false 访问8: 不是5或1,返回false 访问1: 自己是1 → 返回true 访问3: lson=true(来自5), rson=true(来自1) → 满足lson&&rson → ans=3

方法二:父指针 + 哈希表

思路详解

这个方法更直观,模拟了"从下往上找"的过程:

  1. 预处理:遍历整棵树,用哈希表记录每个节点的父节点
  2. 标记路径:从p开始,沿着父指针向上,标记所有访问过的节点
  3. 查找交点:从q开始,沿着父指针向上,第一个被标记的节点就是LCA

🔍为什么这样工作?

  • 从p到根的路径和从q到根的路径,它们的第一个交点就是LCA
  • 这类似于"链表相交"问题的解法
Java 代码
classSolution{// 存储每个节点的父节点privateMap<TreeNode,TreeNode>parentMap=newHashMap<>();// 存储从p到根路径上访问过的节点privateSet<TreeNode>visited=newHashSet<>();publicTreeNodelowestCommonAncestor(TreeNoderoot,TreeNodep,TreeNodeq){// 构建父节点映射buildParentMap(root);// 从p向上遍历,标记所有祖先TreeNodecurrent=p;while(current!=null){visited.add(current);current=parentMap.get(current);}// 从q向上遍历,找到第一个已访问的节点current=q;while(current!=null){if(visited.contains(current)){returncurrent;}current=parentMap.get(current);}returnnull;// 理论上不会执行到这里}/** * DFS遍历构建父节点映射 */privatevoidbuildParentMap(TreeNodenode){if(node==null){return;}if(node.left!=null){parentMap.put(node.left,node);buildParentMap(node.left);}if(node.right!=null){parentMap.put(node.right,node);buildParentMap(node.right);}}}
执行过程图解(示例2)

p=5, q=4为例:

构建父节点映射: 5.parent = 3 6.parent = 5, 2.parent = 5 7.parent = 2, 4.parent = 2 1.parent = 3 0.parent = 1, 8.parent = 1 从p=5向上标记: visited = {5, 3} 从q=4向上查找: 4 → 2 → 5(5在visited中)→ 返回5

五、代码分析与对比

维度递归法父指针法
时间复杂度O(N)O(N)
空间复杂度O(N)O(N)
代码简洁性⭐⭐⭐⭐⭐⭐⭐
实现难度中等简单
函数调用开销有(递归栈)
内存使用仅递归栈哈希表+递归栈
面试推荐度首选备选方案

💡面试策略

  • 优先实现递归法(展示算法思维)
  • 如果面试官要求不用递归,再提供父指针法
  • 重点解释递归法中的LCA判断条件

六、时间复杂度与空间复杂度深度分析

时间复杂度:O(N)

  • 递归法

    • 每个节点被访问一次
    • 每次访问的操作是O(1)
    • 总计:O(N)
  • 父指针法

    • 构建父节点映射:O(N)
    • 从p向上遍历:O(h),h为树高
    • 从q向上遍历:O(h)
    • 总计:O(N)

空间复杂度:O(N)

  • 递归法

    • 递归栈深度:O(h),最坏O(N)(链状树)
    • 全局变量:O(1)
    • 总计:O(N)
  • 父指针法

    • 父节点哈希表:O(N)
    • visited集合:O(h)
    • 递归栈(构建父映射):O(h)
    • 总计:O(N)

📌实际性能差异

  • 递归法常数因子更小,实际运行更快
  • 父指针法在极端情况下(超深树)可能栈溢出风险更低

七、常见问题解答(FAQ)

Q1:为什么递归法使用后序遍历而不是前序遍历?

A:因为我们需要先知道左右子树的信息,才能判断当前节点是否为LCA。后序遍历(左→右→根)保证了在处理根节点时,左右子树的信息已经计算完成。

Q2:递归法中的全局变量ans会不会被多次赋值?

A:理论上会,但实际上只有一次有效赋值。因为后序遍历是从底向上的,第一个满足条件的节点就是深度最大的LCA。即使后续更高层的节点也满足条件,但由于题目保证p和q都存在,所以不会出现多个有效的LCA。

Q3:如果树中有重复值怎么办?

A:题目保证所有Node.val互不相同,所以不用担心。但在实际工程中,我们应该比较节点引用(==)而不是节点值(.val),这在我们的代码中已经正确处理了。

Q4:能否用BFS替代DFS?

A:可以,但实现更复杂。BFS无法自然地进行自底向上的处理,需要额外的数据结构来存储层次信息。

Q5:父指针法的空间复杂度真的是O(N)吗?

A:是的。虽然visited集合最多只存储O(h)个元素,但parentMap需要存储N-1个父节点映射,所以总体空间复杂度是O(N)。


八、优化思路总结

8.1 代码优化

  • 避免全局变量:使用直接返回LCA的递归版本
  • 早期终止:在找到LCA后可以考虑提前终止(但实现复杂,收益不大)
  • 类型安全:始终比较节点引用而非节点值

8.2 性能优化

  • 迭代替代递归:对于超深树,可以用显式栈避免栈溢出
  • 内存优化:父指针法中,visited可以用位图优化(但节点值范围太大,不实用)

8.3 健壮性优化

  • 输入验证:检查p和q是否真的存在于树中(题目已保证)
  • 异常处理:处理空树、空节点等边界情况

九、数据结构与算法基础知识点回顾

9.1 树的遍历方式对比

遍历方式访问顺序适用场景
前序遍历根→左→右复制树、序列化
后序遍历左→右→根删除树、计算子树信息
中序遍历左→根→右BST排序输出

✅ LCA问题天然适合后序遍历,因为需要子树信息来决定根节点的行为。

9.2 树形动态规划(Tree DP)

LCA的递归解法实际上是树形DP的一个简单例子:

  • 状态定义f(root)= 以root为根的子树是否包含p或q
  • 状态转移f(root) = f(left) || f(right) || (root == p || root == q)
  • 答案提取:在状态转移过程中,根据特定条件提取最终答案

9.3 路径相交问题

父指针法将LCA问题转化为两条路径的相交问题

  • 路径1:p → root
  • 路径2:q → root
  • 求两条路径的第一个交点

这与LeetCode 160(相交链表)的思想完全一致。


十、面试官提问环节(模拟)

Q:你的递归解法中,为什么不需要检查p和q是否存在于树中?

A:题目明确说明"p和q均存在于给定的二叉树中",所以我们不需要做额外的存在性检查。但在实际工程中,这是一个重要的边界条件,应该添加相应的验证逻辑。

Q:如果要求找到k个节点的LCA,你的解法还能工作吗?

A:递归法可以扩展。对于k个节点,我们需要统计当前子树中包含了多少个目标节点。当某个节点的子树恰好包含所有k个节点,且它的子节点都不满足这个条件时,该节点就是LCA。

Q:能否将空间复杂度优化到O(1)?

A:在一般二叉树中很难做到O(1)空间复杂度。但如果树节点包含父指针,那么父指针法的空间复杂度可以降到O(h)(只需要visited集合)。不过题目给出的TreeNode定义没有父指针。

Q:你的优化版递归代码中,为什么if (root == null || root == p || root == q)可以直接返回root?

A:这是一个巧妙的剪枝:

  • 如果root为null,说明没找到目标节点
  • 如果root等于p或q,说明找到了目标节点,直接返回
  • 这样可以避免不必要的递归,并且正确处理了"一个节点是另一个节点祖先"的情况

十一、实际开发中的应用场景

11.1 文件系统路径解析

  • 在目录树中,找到两个文件的最近公共目录
  • 实现commonPrefix功能,用于相对路径计算
  • 版本控制系统中的分支合并点查找

11.2 DOM树操作

  • 在HTML DOM树中,找到两个元素的最近公共祖先
  • 实现事件委托时,确定事件处理的最佳位置
  • CSS选择器引擎中的祖先匹配

11.3 组织架构管理

  • 在公司组织架构树中,找到两个员工的最近共同上级
  • 权限系统中的角色继承关系分析
  • 审批流程中的共同审批人确定

11.4 编译器AST(抽象语法树)

  • 在AST中,找到两个表达式的最近公共作用域
  • 变量作用域分析和符号表管理
  • 代码优化中的公共子表达式识别

11.5 网络路由协议

  • 在网络拓扑树中,找到两个节点的最优汇聚点
  • 多播路由中的汇聚路由器选择
  • CDN节点选择中的最优边缘节点确定

十二、相关题目推荐

题号题目关联点
235二叉搜索树的最近公共祖先BST特殊性质
1650二叉树的最近公共祖先 III节点包含父指针
1676二叉树的最近公共祖先 IV多个节点的LCA
1644二叉树的最近公共祖先 IIp或q可能不存在
236本题普通二叉树LCA

🔗 学习路径建议:235 → 236 → 1650 → 1676


十三、总结与延伸

13.1 核心思想提炼

  • 递归思维:自底向上,后序遍历,利用子树信息决策
  • 分类讨论:LCA的两种情况必须都考虑到
  • 树形DP:将复杂问题分解为子问题的典型应用
  • 路径相交:将树问题转化为路径问题的巧妙转换

13.2 延伸思考

  • 动态LCA:支持在线插入/删除节点的LCA查询
  • 批量LCA:预处理后支持O(1)查询任意两点LCA(Tarjan算法、倍增算法)
  • 带权LCA:路径上有权重,求最小权重的公共祖先
  • 分布式LCA:在分布式系统中处理超大树的LCA查询

13.3 面试答题建议

展示完整的思考过程:

  1. 确认理解:“LCA是指深度最大的公共祖先,一个节点可以是自己的祖先,对吗?”
  2. 分析性质:解释LCA的两种情况,画图说明
  3. 提出方案:先说递归思路,强调后序遍历的必要性
  4. 代码实现:写出清晰的递归代码,解释关键条件
  5. 优化讨论:提到父指针法作为备选方案
  6. 复杂度分析:准确分析时间和空间复杂度
  7. 边界处理:讨论特殊情况的处理

LCA问题是树形数据结构的经典代表,它不仅考察了对树遍历的理解,更体现了分治思想和递归思维的精髓。
掌握LCA的解法,就掌握了处理树形结构中"关系查询"问题的核心能力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1179291.shtml

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

相关文章

【LeetCode热题100】Java详解:二叉树中的最大路径和(含递归解法与工程实践)

【LeetCode热题100】Java详解&#xff1a;二叉树中的最大路径和&#xff08;含递归解法与工程实践&#xff09; 面向人群 正在准备技术面试&#xff08;尤其是大厂后端、算法岗&#xff09;的开发者已掌握二叉树基本遍历&#xff0c;希望深入理解路径和问题的学习者刷 LeetCo…

Linux设备管理:从内核驱动到用户空间的完整架构解析 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Python+django的协同过滤算法的 美食菜谱推荐分享平台

目录协同过滤算法在美食菜谱推荐平台的应用系统功能与优化策略开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;协同过滤算法在美食菜谱推荐平台的应用 基于Python和Django框架的美食菜谱推荐…

JavaScript的入门

&#x1f31f; JavaScript 入门&#xff1a;网页互动的魔法语言 &#x1f31f;&#x1f31f; JavaScript 入门&#xff1a;网页互动的魔法语言 &#x1f31f;✨ 什么是 JavaScript&#xff1f;&#x1f4a1; 为什么要学习 JavaScript&#xff1f;&#x1f3af; JavaScript 的基…

Python+django的小区停车场收费车辆计费管理系统的设计与实现

目录小区停车场收费车辆计费管理系统的设计与实现开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;小区停车场收费车辆计费管理系统的设计与实现 该系统基于PythonDjango框架开发&#xff0c…

彼得林奇如何看待公司的股东回报政策

彼得林奇如何看待公司的股东回报政策关键词&#xff1a;彼得林奇、股东回报政策、股息、股票回购、公司价值、投资策略、财务分析摘要&#xff1a;本文深入探讨了投资大师彼得林奇对公司股东回报政策的观点。通过研究彼得林奇的投资理念和方法&#xff0c;阐述了股东回报政策在…

Python+django的小区车辆停车场车位预约管理系统 可视化

目录 系统概述核心功能模块技术实现亮点应用价值 开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 系统概述 PythonDjango开发的小区车辆停车场车位预约管理系统旨在通过数字化手段优化车位…

2026年国产时序数据库盘点-深入剖析融合多模架构

摘要&#xff1a;进入2026年&#xff0c;在“数字中国”与工业物联网浪潮的强劲推动下&#xff0c;国产时序数据库市场持续繁荣&#xff0c;竞争格局日趋清晰。本文将对当前主流的国产时序数据库进行梳理盘点&#xff0c;并特别聚焦于金仓数据库&#xff08;Kingbase&#xff0…

前端性能优化指南:从加载到交互的每一毫秒

前言 上个月&#xff0c;我们的产品被反馈"页面加载太慢"。用户在3G网络下需要等待8秒才能看到内容。 经过一个月的优化&#xff0c;我们把首屏加载时间从8秒降到了1.2秒。这篇文章分享我们的优化实践。 一、性能指标体系 1.1 核心Web指标&#xff08;Core Web Vi…

【LeetCode热题100】Java详解:二叉树的右视图(含BFS/DFS双解法与工程实践)

【LeetCode热题100】Java详解&#xff1a;二叉树的右视图&#xff08;含BFS/DFS双解法与工程实践&#xff09; 面向人群 正在准备技术面试&#xff08;尤其是大厂后端、算法岗&#xff09;的开发者已掌握基础二叉树操作&#xff0c;希望深入理解层序遍历与空间优化技巧的学习…

Docker容器化实战:从入门到生产环境部署

前言 两年前&#xff0c;我们公司的部署流程是这样的&#xff1a;开发在本地调试好代码&#xff0c;打包发给运维&#xff0c;运维在服务器上配置环境&#xff0c;然后发现"在我机器上能跑"。 引入Docker后&#xff0c;一切都变了。这篇文章分享我们的容器化实践经…

栈的一个magic gadget的运用以及数组越界

the end???.text:0000000000400658 add [rbp-3Dh], ebx .text:000000000040065B nop .text:000000000040065C retn这个gadget就比较常见了,就是把ebx的值加给…

亲测好用!自考论文必备TOP9 AI论文工具深度测评

亲测好用&#xff01;自考论文必备TOP9 AI论文工具深度测评 一、不同维度核心推荐&#xff1a;9款AI工具各有所长 自考论文写作是一个系统性工程&#xff0c;从选题到开题、初稿撰写、查重降重再到最终排版&#xff0c;每一个环节都需要合适的工具辅助。而市面上的AI论文工具功…

【LeetCode热题100】Java详解:二叉树展开为链表(含O(1)空间原地解法与工程实践)

【LeetCode热题100】Java详解&#xff1a;二叉树展开为链表&#xff08;含O(1)空间原地解法与工程实践&#xff09; 面向人群 正在准备技术面试&#xff08;尤其是大厂后端、算法岗&#xff09;的开发者已掌握二叉树基本操作&#xff0c;希望深入理解原地算法与指针操作技巧的…

文献阅读:Class-incremental Learning for Time Series:Benchmark and Evaluation

摘要 现实世界的环境本质上是不稳定的&#xff0c;随着时间的推移经常引入新的类别。 这在时间序列分类中尤其常见&#xff0c;例如医疗保健中新疾病分类的出现或人类活动识别中添加新活动。 在这种情况下&#xff0c;需要一个学习系统来有效地吸收新的类&#xff0c;同时避免…

Day84(10)-F:\硕士阶段\Java\课程资料\7、Redis入门到实战教程\Redis-笔记资料\03-高级篇\资料\item-service-多级缓存

安装和配置Canal 下面我们就开启mysql的主从同步机制,让Canal来模拟salve 1.开启MySQL主从 Canal是基于MySQL的主从同步功能,因此必须先开启MySQL的主从功能才可以。 这里以之前用Docker运行的mysql为例: 1.1.开启b…

【LeetCode热题100】Java详解:二叉搜索树中第K小的元素(含进阶优化与面试延伸)

【LeetCode热题100】Java详解&#xff1a;二叉搜索树中第K小的元素&#xff08;含进阶优化与面试延伸&#xff09; 面向人群 正在准备技术面试&#xff08;尤其是大厂算法岗、后端开发岗&#xff09;的程序员已掌握基础数据结构&#xff0c;希望深入理解二叉搜索树及其应用场…

如何提高图像识别的准确率?

你想了解的是如何提升图像识别(以MNIST手写数字识别为例)的准确率,核心是从数据、模型、训练策略、正则化四个维度优化,解决“欠拟合”(准确率低)、“过拟合”(训练准、测试差)两大核心问题。下面我会结合MNIS…

数据结构入门:时间复杂度与排序和查找 - 详解

数据结构入门:时间复杂度与排序和查找 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &q…

STM32单片机16*16汉字点阵广告牌75(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码

STM32单片机16*16汉字点阵广告牌75(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_文章底部可以扫码 产品功能描述&#xff1a; 本系统由STM32F103C8T6单片机核心板、16*16点阵屏显示模块、按键及电源组成。 1、通过按键可以切换点阵屏显示内容…