并查集模板:find / union / count 从零推导

副标题 / 摘要 并查集不是从 find 和 union 这两个函数名开始背。它要解决的问题是:怎样判断两个点是否已经属于同一个集合,并在看到一条连接关系时把两个集合合并。 预计阅读时长:10~12 分钟 标签:Hot100、并查集、Union-Find、DSU、图 SEO 关键词:并查集, Union-Find, DSU, find, union, count, 路径压缩 元描述:用 Python 从零推导并查集模板,讲清 parent、find、union、count、路径压缩和连通分量计数。 A — Algorithm(从集合合并压力开始) 小任务:不断合并集合,并回答连通性 假设有 5 个点: 0, 1, 2, 3, 4 一开始,每个点都是一个独立集合: {0}, {1}, {2}, {3}, {4} 现在依次发生两次合并: union(0, 1) union(1, 2) 我们想回答三个问题: 0 和 2 是否在同一个集合? 3 和 4 是否在同一个集合? 当前一共有几个集合? 手工看答案是: {0, 1, 2}, {3}, {4} 所以: 0 和 2 在同一个集合 3 和 4 不在同一个集合 当前 count = 3 这就是并查集要解决的核心任务: ...

2026年6月30日 · 5 分钟 · map[name:Jeanphilo]

LeetCode 208:实现 Trie(前缀树)模板题解析

副标题 / 摘要 208 题的难点不在算法变化,而在把 Trie 模板按平台接口写稳:insert 建路径,search 查完整单词,startsWith 只查前缀路径。 预计阅读时长:8~10 分钟 标签:Hot100、Trie、前缀树、LeetCode 208 SEO 关键词:LeetCode 208, Implement Trie, Prefix Tree, startsWith 元描述:从接口要求出发实现 LeetCode 208,讲清 Trie 节点、children、is_end、insert/search/startsWith 的区别。 A — Algorithm(题目与算法) 先看最小操作压力 208 题最关键的操作序列是: Trie trie = new Trie() trie.insert("apple") trie.search("apple") -> true trie.search("app") -> false trie.startsWith("app") -> true trie.insert("app") trie.search("app") -> true 这个例子说明: app 可以是 apple 的前缀 但只有插入过 app 后,search("app") 才能返回 True 所以这题不是“路径存在就算命中”。 我们必须同时维护: 路径是否存在 这条路径是否刚好是完整单词 题目接口 设计一个 Trie,也叫前缀树,支持三个操作: ...

2026年6月24日 · 3 分钟 · map[name:Jeanphilo]

Trie 模板:从节点字段到插入查询 invariant

副标题 / 摘要 Trie 的重点不是背代码,而是理解“一个节点代表一个前缀”。只要这个模型稳定,插入、完整单词查询和前缀查询都会变成同一个循环。 预计阅读时长:8~10 分钟 标签:Hot100、Trie、前缀树、字典树 SEO 关键词:Trie, 前缀树, 字典树, children, is_end 元描述:用 Python 写一个最小 Trie 模板,讲清节点字段、children 走法、结束标记和循环 invariant。 A — Algorithm(从一个小任务开始) 小任务:同时回答完整单词和前缀 假设已经插入: app apple bat 现在要问: app 是不是完整单词? ap 是不是某个单词的前缀? apply 是否存在? 这个小任务暴露了两个缺口: 只用哈希集合,可以快速判断完整单词,但不能自然回答前缀问题 只看路径存在,又会把 app 和 apple 的前缀关系混成一件事 Trie 要解决的就是:让很多字符串共享公共前缀,同时还能区分“前缀存在”和“完整单词存在”。 从压力反推要支持什么 我们先不管任何题目接口,只定义一个自己的模板: insert(word):把一个单词插入 Trie search(word):判断完整单词是否存在 starts_with(prefix):判断是否存在以 prefix 开头的单词 最小结构图 插入 app 和 apple 后,结构可以想成: root └─ a └─ p └─ p [end] └─ l └─ e [end] 这里最重要的是: ...

2026年6月24日 · 3 分钟 · map[name:Jeanphilo]

LeetCode 547:省份数量,把邻接矩阵看成图的连通分量

题目要求 给你一个 n x n 的矩阵 isConnected,其中有 n 个城市。 如果 isConnected[i][j] == 1,说明城市 i 和城市 j 直接相连;如果两个城市可以通过若干个直接相连的城市互相到达,它们就属于同一个省份。 题目要求返回省份的总数。 这里最容易误解的一点是:题目不是让我们数矩阵里有多少个 1,也不是只看直接相连的城市对。它真正要数的是: 直接或间接连接在一起的城市组有多少个。 换成图的语言,就是: 给定一个无向图的邻接矩阵,返回这个图的连通分量数量。 输入输出 输入:isConnected: List[List[int]] 输出:省份数量 int 城市编号可以按 0..n-1 理解。 isConnected[i][j] == 1 表示城市 i 和城市 j 之间有边。 isConnected[i][j] == 0 表示城市 i 和城市 j 没有直接边。 示例 1 输入:isConnected = [ [1, 1, 0], [1, 1, 0], [0, 0, 1] ] 输出:2 城市 0 和城市 1 直接相连,所以它们属于同一个省份。 ...

2026年5月27日 · 6 分钟 · map[name:Jeanphilo]

LeetCode 198:打家劫舍,从偷或不偷推出一维 DP

题目要求 输入输出 输入:整数数组 nums nums[i] 表示第 i 间房子的金额 不能偷相邻房子 输出:返回最多能偷到的金额 约束:1 <= nums.length <= 100,0 <= nums[i] <= 400 示例 输入:nums = [1,2,3,1] 输出:4 解释:偷下标 0 和下标 2,金额 1 + 3 = 4 输入:nums = [2,7,9,3,1] 输出:12 解释:偷下标 0、2、4,金额 2 + 9 + 1 = 12 这篇只用 Python,从这个二选一冲突推出一维 DP。 从 [1,2,3,1] 的相邻冲突开始 看例子: nums = [1,2,3,1] 如果偷下标 2 的房子,金额是 3,那么下标 1 和下标 3 都不能偷。 如果不偷下标 2,答案可能来自前面下标 0..1 的最优结果。 所以走到某一间房时,核心选择只有两个: ...

2026年5月3日 · 3 分钟 · map[name:Jeanphilo]

LeetCode 53:最大子数组和,从 dp[i] 含义推出 Kadane

题目要求 输入输出 输入:整数数组 nums 输出:返回连续非空子数组的最大和 子数组必须连续 至少选一个元素 约束:1 <= nums.length <= 10^5,-10^4 <= nums[i] <= 10^4 示例 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:[4,-1,2,1] 的和最大,为 6 输入:nums = [1] 输出:1 这篇只用 Python,从 dp[i] 的含义一步一步推出 Kadane 算法。 从 [-2,1,-3,4] 的断点开始 看一个前缀例子: nums = [-2, 1, -3, 4] 如果走到 4,有两种选择: 把前面的某个连续子数组接到 4 前面 从 4 自己重新开始 这里的关键不是“所有子数组怎么枚举”,而是: 当我们决定一个最优子数组必须以当前位置结尾时,它的来源只有两种:接上前一个位置的最优结尾,或者从当前位置重新开始。 这就是一维 DP 的入口。 Step 1:先定义一个更小的问题 直接问“整个数组的最大子数组和是多少”太大。我们先强加一个限制: 如果子数组必须以第 i 个元素结尾,它的最大和是多少? 这个值记为 dp[i]。 这里的 dp[i] 不是“前 i 个元素里的最大子数组和”。它更窄: ...

2026年5月3日 · 3 分钟 · map[name:Jeanphilo]

LeetCode 78:子集,从搜索树推出 startIndex 回溯模板

题目要求 输入输出 输入:nums,长度 1 <= nums.length <= 10 元素范围:-10 <= nums[i] <= 10 所有元素互不相同 输出:返回 nums 的所有可能子集 顺序:结果顺序不限,子集内部不需要考虑排列顺序 示例 输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] 这篇只用 Python 构造一个最小可运行解法。 从 [1,2] 的搜索树开始 最小但有分叉的例子是: nums = [1,2] 答案应该是: [], [1], [1,2], [2] 注意这里没有 [2,1]。这说明问题不是“随便排列所有选择”,而是“每个元素选或不选,且同一组元素只出现一次”。 如果把构造过程画成树: [] |- [1] | |- [1,2] |- [2] 这棵树暴露出两个事实: 每个节点都是一个合法子集。 进入 [1] 之后,后面只能继续看 2,不能回头再选 1 或生成 [2,1]。 Step 1:先说清楚一个递归层在解决什么 如果当前已经选出一个 path,剩下的问题是: 从某个起点之后的元素里,继续选择若干个元素,扩展当前 path。 这个“某个起点”必须被记下来。否则 [1,2] 和 [2,1] 会从不同路径重复出现。 先写最小骨架。它只保留两个状态: ...

2026年5月3日 · 3 分钟 · map[name:Jeanphilo]

LeetCode 90:子集 II,从重复分支推出层内去重

题目要求 输入输出 输入:nums,长度 1 <= nums.length <= 10 元素范围:-10 <= nums[i] <= 10 nums 可能有重复元素 输出:返回所有不重复子集 顺序:结果顺序不限,但同一个值序列的子集只能出现一次 示例 输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] 这篇只用 Python,从一个能跑但浪费的版本,一步一步过渡到排序 + 层内去重。 从 [1,2,2] 的重复分支开始 最小能暴露问题的例子是: nums = [1,2,2] 如果直接照搬 78. 子集 的模板,搜索树里会出现两条不同分支: 选择下标 1 的 2 -> [2] 选择下标 2 的 2 -> [2] 这两个分支路径不同,但值序列相同,最终答案重复。 所以这题真正新增的问题不是“会不会回溯”,而是: 怎样跳过重复分支,同时保留 [2,2] 这种合法答案? Step 1:先复用 78 的状态,看看哪里会坏 当前部分答案仍然需要 path;当前层仍然需要 start 控制只能往右选。 先写出 78 的核心版本: def dfs(start: int) -> None: res.append(path.copy()) for i in range(start, len(nums)): path.append(nums[i]) dfs(i + 1) path.pop() 这个版本在 nums 全部互不相同时是正确的。 ...

2026年5月3日 · 3 分钟 · map[name:Jeanphilo]

Hot100:从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal)索引分治 ACERS 解析

副标题 / 摘要 LeetCode 105 的关键不是死记“前序 + 中序能构树”,而是先看懂两种遍历各自提供什么信息。前序负责告诉你谁是根,中序负责告诉你左右边界,组合起来就能自然落成一个哈希定位的区间分治。 预计阅读时长:12~15 分钟 标签:Hot100、二叉树、分治、哈希表、前序遍历 SEO 关键词:Construct Binary Tree from Preorder and Inorder Traversal, 从前序与中序遍历序列构造二叉树, 前序遍历, 中序遍历, 分治, LeetCode 105 元描述:系统讲透 LeetCode 105 的构树推导、索引分治、哈希优化与多语言实现,并解释为什么一定要先建左子树。 A — Algorithm(题目与算法) 题目还原 给定一棵二叉树的前序遍历 preorder 和中序遍历 inorder,请你重建这棵二叉树并返回它的根节点。 题目保证: 树中元素没有重复 给出的两个数组一定来自同一棵合法二叉树 输入输出 名称 类型 描述 preorder int[] 前序遍历结果 inorder int[] 中序遍历结果 返回值 TreeNode 重建后的二叉树根节点 示例 1 输入:preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] 输出:[3,9,20,null,null,15,7] 示例 2 输入:preorder = [-1], inorder = [-1] 输出:[-1] 提示 1 <= preorder.length <= 3000 inorder.length == preorder.length -3000 <= preorder[i], inorder[i] <= 3000 preorder 和 inorder 都没有重复元素 inorder 中的每个值都出现在 preorder 中 preorder 保证是某棵树的前序遍历 inorder 保证是同一棵树的中序遍历 目标读者 一看到“根据遍历结果构树”就会混淆前序和中序职责的学习者 想把“树的遍历顺序”和“树的结构恢复”建立稳定联系的开发者 做过 94、114 之后,想继续巩固树结构题的读者 背景 / 动机 这题最值得学的地方是: ...

2026年4月20日 · 9 分钟 · map[name:Jeanphilo]

Hot100:二叉树的右视图(Binary Tree Right Side View)层序遍历取每层最后一个 ACERS 解析

副标题 / 摘要 LeetCode 199 不是在考“看图想象力”,而是在考你能不能把视角问题翻译成层级问题。只要意识到右视图就是每一层最右边那个节点,这题就会立刻变成一个标准层序遍历。 预计阅读时长:10~13 分钟 标签:Hot100、二叉树、BFS、层序遍历、队列 SEO 关键词:Binary Tree Right Side View, 二叉树的右视图, 层序遍历, BFS, 右优先 DFS, LeetCode 199 元描述:系统讲透 LeetCode 199 的层序遍历解法,解释“右视图 = 每层最后一个节点”的本质,并补充右优先 DFS 视角。 A — Algorithm(题目与算法) 题目还原 给定一棵二叉树的根节点 root,想象你站在它的右侧,从上到下观察这棵树,返回你能看到的节点值。 输入输出 名称 类型 描述 root TreeNode 二叉树根节点 返回值 int[] 从上到下看到的右视图节点值 示例 1 输入:root = [1,2,3,null,5,null,4] 输出:[1,3,4] 示例 2 输入:root = [1,2,3,4,null,null,null,5] 输出:[1,3,4,5] 示例 3 输入:root = [1,null,3] 输出:[1,3] 示例 4 输入:root = [] 输出:[] 提示 二叉树的节点个数范围是 [0, 100] -100 <= Node.val <= 100 目标读者 已经会层序遍历,但不够熟悉“每层保留哪个节点”这类变形题的学习者 一看到“从某个方向看到的节点”就容易被题面叙述绕进去的开发者 想把 102 + 199 这一组 BFS 树题系统化的读者 背景 / 动机 这题很适合练习一个非常重要的动作: ...

2026年4月20日 · 8 分钟 · map[name:Jeanphilo]