vault backup: 2026-07-13 10:00:42
This commit is contained in:
@@ -15,217 +15,217 @@
|
||||
|
||||
### 哈希
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 01 | [两数之和](https://leetcode.cn/problems/two-sum/) | 🟢 简单 | [[哈希/01-两数之和]] |
|
||||
| 02 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | 🟡 中等 | [[哈希/02-字母异位词分组]] |
|
||||
| 03 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | 🟡 中等 | [[哈希/03-最长连续序列]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 01 | [两数之和](https://leetcode.cn/problems/two-sum/) | 🟢 简单 | [[哈希/01-两数之和]] | [01-两数之和](哈希/01-两数之和.md) |
|
||||
| 02 | [字母异位词分组](https://leetcode.cn/problems/group-anagrams/) | 🟡 中等 | [[哈希/02-字母异位词分组]] | [02-字母异位词分组](哈希/02-字母异位词分组.md) |
|
||||
| 03 | [最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/) | 🟡 中等 | [[哈希/03-最长连续序列]] | [03-最长连续序列](哈希/03-最长连续序列.md) |
|
||||
|
||||
---
|
||||
|
||||
### 双指针
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 04 | [移动零](https://leetcode.cn/problems/move-zeroes/) | 🟢 简单 | [[双指针/04-移动零]] |
|
||||
| 05 | [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | 🟡 中等 | [[双指针/05-盛最多水的容器]] |
|
||||
| 06 | [三数之和](https://leetcode.cn/problems/3sum/) | 🟡 中等 | [[双指针/06-三数之和]] |
|
||||
| 07 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | 🔴 困难 | [[双指针/07-接雨水]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 04 | [移动零](https://leetcode.cn/problems/move-zeroes/) | 🟢 简单 | [[双指针/04-移动零]] | [04-移动零](双指针/04-移动零.md) |
|
||||
| 05 | [盛最多水的容器](https://leetcode.cn/problems/container-with-most-water/) | 🟡 中等 | [[双指针/05-盛最多水的容器]] | [05-盛最多水的容器](双指针/05-盛最多水的容器.md) |
|
||||
| 06 | [三数之和](https://leetcode.cn/problems/3sum/) | 🟡 中等 | [[双指针/06-三数之和]] | [06-三数之和](双指针/06-三数之和.md) |
|
||||
| 07 | [接雨水](https://leetcode.cn/problems/trapping-rain-water/) | 🔴 困难 | [[双指针/07-接雨水]] | [07-接雨水](双指针/07-接雨水.md) |
|
||||
|
||||
---
|
||||
|
||||
### 滑动窗口
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 08 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | 🟡 中等 | [[滑动窗口/08-无重复字符的最长子串]] |
|
||||
| 09 | [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | 🟡 中等 | [[滑动窗口/09-找到字符串中所有字母异位词]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 08 | [无重复字符的最长子串](https://leetcode.cn/problems/longest-substring-without-repeating-characters/) | 🟡 中等 | [[滑动窗口/08-无重复字符的最长子串]] | [08-无重复字符的最长子串](滑动窗口/08-无重复字符的最长子串.md) |
|
||||
| 09 | [找到字符串中所有字母异位词](https://leetcode.cn/problems/find-all-anagrams-in-a-string/) | 🟡 中等 | [[滑动窗口/09-找到字符串中所有字母异位词]] | [09-找到字符串中所有字母异位词](滑动窗口/09-找到字符串中所有字母异位词.md) |
|
||||
|
||||
---
|
||||
|
||||
### 子串
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 10 | [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | 🟡 中等 | [[子串/10-和为 K 的子数组]] |
|
||||
| 11 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | 🔴 困难 | [[子串/11-滑动窗口最大值]] |
|
||||
| 12 | [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | 🔴 困难 | [[子串/12-最小覆盖子串]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 10 | [和为 K 的子数组](https://leetcode.cn/problems/subarray-sum-equals-k/) | 🟡 中等 | [[子串/10-和为 K 的子数组]] | [10-和为 K 的子数组](子串/10-和为 K 的子数组.md) |
|
||||
| 11 | [滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum/) | 🔴 困难 | [[子串/11-滑动窗口最大值]] | [11-滑动窗口最大值](子串/11-滑动窗口最大值.md) |
|
||||
| 12 | [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) | 🔴 困难 | [[子串/12-最小覆盖子串]] | [12-最小覆盖子串](子串/12-最小覆盖子串.md) |
|
||||
|
||||
---
|
||||
|
||||
### 普通数组
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 13 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | 🟡 中等 | [[普通数组/13-最大子数组和]] |
|
||||
| 14 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | 🟡 中等 | [[普通数组/14-合并区间]] |
|
||||
| 15 | [轮转数组](https://leetcode.cn/problems/rotate-array/) | 🟡 中等 | [[普通数组/15-轮转数组]] |
|
||||
| 16 | [除了自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | 🟡 中等 | [[普通数组/16-除了自身以外数组的乘积]] |
|
||||
| 17 | [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | 🔴 困难 | [[普通数组/17-缺失的第一个正数]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 13 | [最大子数组和](https://leetcode.cn/problems/maximum-subarray/) | 🟡 中等 | [[普通数组/13-最大子数组和]] | [13-最大子数组和](普通数组/13-最大子数组和.md) |
|
||||
| 14 | [合并区间](https://leetcode.cn/problems/merge-intervals/) | 🟡 中等 | [[普通数组/14-合并区间]] | [14-合并区间](普通数组/14-合并区间.md) |
|
||||
| 15 | [轮转数组](https://leetcode.cn/problems/rotate-array/) | 🟡 中等 | [[普通数组/15-轮转数组]] | [15-轮转数组](普通数组/15-轮转数组.md) |
|
||||
| 16 | [除了自身以外数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/) | 🟡 中等 | [[普通数组/16-除了自身以外数组的乘积]] | [16-除了自身以外数组的乘积](普通数组/16-除了自身以外数组的乘积.md) |
|
||||
| 17 | [缺失的第一个正数](https://leetcode.cn/problems/first-missing-positive/) | 🔴 困难 | [[普通数组/17-缺失的第一个正数]] | [17-缺失的第一个正数](普通数组/17-缺失的第一个正数.md) |
|
||||
|
||||
---
|
||||
|
||||
### 矩阵
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 18 | [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) | 🟡 中等 | [[矩阵/18-矩阵置零]] |
|
||||
| 19 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | 🟡 中等 | [[矩阵/19-螺旋矩阵]] |
|
||||
| 20 | [旋转图像](https://leetcode.cn/problems/rotate-image/) | 🟡 中等 | [[矩阵/20-旋转图像]] |
|
||||
| 21 | [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | 🟡 中等 | [[矩阵/21-搜索二维矩阵 II]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 18 | [矩阵置零](https://leetcode.cn/problems/set-matrix-zeroes/) | 🟡 中等 | [[矩阵/18-矩阵置零]] | [18-矩阵置零](矩阵/18-矩阵置零.md) |
|
||||
| 19 | [螺旋矩阵](https://leetcode.cn/problems/spiral-matrix/) | 🟡 中等 | [[矩阵/19-螺旋矩阵]] | [19-螺旋矩阵](矩阵/19-螺旋矩阵.md) |
|
||||
| 20 | [旋转图像](https://leetcode.cn/problems/rotate-image/) | 🟡 中等 | [[矩阵/20-旋转图像]] | [20-旋转图像](矩阵/20-旋转图像.md) |
|
||||
| 21 | [搜索二维矩阵 II](https://leetcode.cn/problems/search-a-2d-matrix-ii/) | 🟡 中等 | [[矩阵/21-搜索二维矩阵 II]] | [21-搜索二维矩阵 II](矩阵/21-搜索二维矩阵 II.md) |
|
||||
|
||||
---
|
||||
|
||||
### 链表
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 22 | [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | 🟢 简单 | [[链表/22-相交链表]] |
|
||||
| 23 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | 🟢 简单 | [[链表/23-反转链表]] |
|
||||
| 24 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | 🟢 简单 | [[链表/24-回文链表]] |
|
||||
| 25 | [环形链表](https://leetcode.cn/problems/linked-list-cycle/) | 🟢 简单 | [[链表/25-环形链表]] |
|
||||
| 26 | [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | 🟡 中等 | [[链表/26-环形链表 II]] |
|
||||
| 27 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | 🟢 简单 | [[链表/27-合并两个有序链表]] |
|
||||
| 28 | [两数相加](https://leetcode.cn/problems/add-two-numbers/) | 🟡 中等 | [[链表/28-两数相加]] |
|
||||
| 29 | [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | 🟡 中等 | [[链表/29-删除链表的倒数第 N 个结点]] |
|
||||
| 30 | [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | 🟡 中等 | [[链表/30-两两交换链表中的节点]] |
|
||||
| 31 | [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | 🔴 困难 | [[链表/31-K 个一组翻转链表]] |
|
||||
| 32 | [随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/) | 🟡 中等 | [[链表/32-随机链表的复制]] |
|
||||
| 33 | [排序链表](https://leetcode.cn/problems/sort-list/) | 🟡 中等 | [[链表/33-排序链表]] |
|
||||
| 34 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | 🔴 困难 | [[链表/34-合并 K 个升序链表]] |
|
||||
| 35 | [LRU 缓存](https://leetcode.cn/problems/lru-cache/) | 🟡 中等 | [[链表/35-LRU 缓存]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 22 | [相交链表](https://leetcode.cn/problems/intersection-of-two-linked-lists/) | 🟢 简单 | [[链表/22-相交链表]] | [22-相交链表](链表/22-相交链表.md) |
|
||||
| 23 | [反转链表](https://leetcode.cn/problems/reverse-linked-list/) | 🟢 简单 | [[链表/23-反转链表]] | [23-反转链表](链表/23-反转链表.md) |
|
||||
| 24 | [回文链表](https://leetcode.cn/problems/palindrome-linked-list/) | 🟢 简单 | [[链表/24-回文链表]] | [24-回文链表](链表/24-回文链表.md) |
|
||||
| 25 | [环形链表](https://leetcode.cn/problems/linked-list-cycle/) | 🟢 简单 | [[链表/25-环形链表]] | [25-环形链表](链表/25-环形链表.md) |
|
||||
| 26 | [环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/) | 🟡 中等 | [[链表/26-环形链表 II]] | [26-环形链表 II](链表/26-环形链表 II.md) |
|
||||
| 27 | [合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/) | 🟢 简单 | [[链表/27-合并两个有序链表]] | [27-合并两个有序链表](链表/27-合并两个有序链表.md) |
|
||||
| 28 | [两数相加](https://leetcode.cn/problems/add-two-numbers/) | 🟡 中等 | [[链表/28-两数相加]] | [28-两数相加](链表/28-两数相加.md) |
|
||||
| 29 | [删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) | 🟡 中等 | [[链表/29-删除链表的倒数第 N 个结点]] | [29-删除链表的倒数第 N 个结点](链表/29-删除链表的倒数第 N 个结点.md) |
|
||||
| 30 | [两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/) | 🟡 中等 | [[链表/30-两两交换链表中的节点]] | [30-两两交换链表中的节点](链表/30-两两交换链表中的节点.md) |
|
||||
| 31 | [K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/) | 🔴 困难 | [[链表/31-K 个一组翻转链表]] | [31-K 个一组翻转链表](链表/31-K 个一组翻转链表.md) |
|
||||
| 32 | [随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/) | 🟡 中等 | [[链表/32-随机链表的复制]] | [32-随机链表的复制](链表/32-随机链表的复制.md) |
|
||||
| 33 | [排序链表](https://leetcode.cn/problems/sort-list/) | 🟡 中等 | [[链表/33-排序链表]] | [33-排序链表](链表/33-排序链表.md) |
|
||||
| 34 | [合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/) | 🔴 困难 | [[链表/34-合并 K 个升序链表]] | [34-合并 K 个升序链表](链表/34-合并 K 个升序链表.md) |
|
||||
| 35 | [LRU 缓存](https://leetcode.cn/problems/lru-cache/) | 🟡 中等 | [[链表/35-LRU 缓存]] | [35-LRU 缓存](链表/35-LRU 缓存.md) |
|
||||
|
||||
---
|
||||
|
||||
### 二叉树
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 36 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | 🟢 简单 | [[二叉树/36-二叉树的中序遍历]] |
|
||||
| 37 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | 🟢 简单 | [[二叉树/37-二叉树的最大深度]] |
|
||||
| 38 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | 🟢 简单 | [[二叉树/38-翻转二叉树]] |
|
||||
| 39 | [对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | 🟢 简单 | [[二叉树/39-对称二叉树]] |
|
||||
| 40 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | 🟢 简单 | [[二叉树/40-二叉树的直径]] |
|
||||
| 41 | [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | 🟡 中等 | [[二叉树/41-二叉树的层序遍历]] |
|
||||
| 42 | [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | 🟢 简单 | [[二叉树/42-将有序数组转换为二叉搜索树]] |
|
||||
| 43 | [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | 🟡 中等 | [[二叉树/43-验证二叉搜索树]] |
|
||||
| 44 | [二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | 🟡 中等 | [[二叉树/44-二叉搜索树中第 K 小的元素]] |
|
||||
| 45 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | 🟡 中等 | [[二叉树/45-二叉树的右视图]] |
|
||||
| 46 | [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | 🟡 中等 | [[二叉树/46-二叉树展开为链表]] |
|
||||
| 47 | [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | 🟡 中等 | [[二叉树/47-从前序与中序遍历序列构造二叉树]] |
|
||||
| 48 | [路径总和 III](https://leetcode.cn/problems/path-sum-iii/) | 🟡 中等 | [[二叉树/48-路径总和 III]] |
|
||||
| 49 | [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | 🟡 中等 | [[二叉树/49-二叉树的最近公共祖先]] |
|
||||
| 50 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | 🔴 困难 | [[二叉树/50-二叉树中的最大路径和]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 36 | [二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) | 🟢 简单 | [[二叉树/36-二叉树的中序遍历]] | [36-二叉树的中序遍历](二叉树/36-二叉树的中序遍历.md) |
|
||||
| 37 | [二叉树的最大深度](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) | 🟢 简单 | [[二叉树/37-二叉树的最大深度]] | [37-二叉树的最大深度](二叉树/37-二叉树的最大深度.md) |
|
||||
| 38 | [翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/) | 🟢 简单 | [[二叉树/38-翻转二叉树]] | [38-翻转二叉树](二叉树/38-翻转二叉树.md) |
|
||||
| 39 | [对称二叉树](https://leetcode.cn/problems/symmetric-tree/) | 🟢 简单 | [[二叉树/39-对称二叉树]] | [39-对称二叉树](二叉树/39-对称二叉树.md) |
|
||||
| 40 | [二叉树的直径](https://leetcode.cn/problems/diameter-of-binary-tree/) | 🟢 简单 | [[二叉树/40-二叉树的直径]] | [40-二叉树的直径](二叉树/40-二叉树的直径.md) |
|
||||
| 41 | [二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/) | 🟡 中等 | [[二叉树/41-二叉树的层序遍历]] | [41-二叉树的层序遍历](二叉树/41-二叉树的层序遍历.md) |
|
||||
| 42 | [将有序数组转换为二叉搜索树](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) | 🟢 简单 | [[二叉树/42-将有序数组转换为二叉搜索树]] | [42-将有序数组转换为二叉搜索树](二叉树/42-将有序数组转换为二叉搜索树.md) |
|
||||
| 43 | [验证二叉搜索树](https://leetcode.cn/problems/validate-binary-search-tree/) | 🟡 中等 | [[二叉树/43-验证二叉搜索树]] | [43-验证二叉搜索树](二叉树/43-验证二叉搜索树.md) |
|
||||
| 44 | [二叉搜索树中第 K 小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/) | 🟡 中等 | [[二叉树/44-二叉搜索树中第 K 小的元素]] | [44-二叉搜索树中第 K 小的元素](二叉树/44-二叉搜索树中第 K 小的元素.md) |
|
||||
| 45 | [二叉树的右视图](https://leetcode.cn/problems/binary-tree-right-side-view/) | 🟡 中等 | [[二叉树/45-二叉树的右视图]] | [45-二叉树的右视图](二叉树/45-二叉树的右视图.md) |
|
||||
| 46 | [二叉树展开为链表](https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/) | 🟡 中等 | [[二叉树/46-二叉树展开为链表]] | [46-二叉树展开为链表](二叉树/46-二叉树展开为链表.md) |
|
||||
| 47 | [从前序与中序遍历序列构造二叉树](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) | 🟡 中等 | [[二叉树/47-从前序与中序遍历序列构造二叉树]] | [47-从前序与中序遍历序列构造二叉树](二叉树/47-从前序与中序遍历序列构造二叉树.md) |
|
||||
| 48 | [路径总和 III](https://leetcode.cn/problems/path-sum-iii/) | 🟡 中等 | [[二叉树/48-路径总和 III]] | [48-路径总和 III](二叉树/48-路径总和 III.md) |
|
||||
| 49 | [二叉树的最近公共祖先](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) | 🟡 中等 | [[二叉树/49-二叉树的最近公共祖先]] | [49-二叉树的最近公共祖先](二叉树/49-二叉树的最近公共祖先.md) |
|
||||
| 50 | [二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/) | 🔴 困难 | [[二叉树/50-二叉树中的最大路径和]] | [50-二叉树中的最大路径和](二叉树/50-二叉树中的最大路径和.md) |
|
||||
|
||||
---
|
||||
|
||||
### 图论
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 51 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | 🟡 中等 | [[图论/51-岛屿数量]] |
|
||||
| 52 | [腐烂的橘子](https://leetcode.cn/problems/rotting-oranges/) | 🟡 中等 | [[图论/52-腐烂的橘子]] |
|
||||
| 53 | [课程表](https://leetcode.cn/problems/course-schedule/) | 🟡 中等 | [[图论/53-课程表]] |
|
||||
| 54 | [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | 🟡 中等 | [[图论/54-实现 Trie (前缀树)]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 51 | [岛屿数量](https://leetcode.cn/problems/number-of-islands/) | 🟡 中等 | [[图论/51-岛屿数量]] | [51-岛屿数量](图论/51-岛屿数量.md) |
|
||||
| 52 | [腐烂的橘子](https://leetcode.cn/problems/rotting-oranges/) | 🟡 中等 | [[图论/52-腐烂的橘子]] | [52-腐烂的橘子](图论/52-腐烂的橘子.md) |
|
||||
| 53 | [课程表](https://leetcode.cn/problems/course-schedule/) | 🟡 中等 | [[图论/53-课程表]] | [53-课程表](图论/53-课程表.md) |
|
||||
| 54 | [实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/) | 🟡 中等 | [[图论/54-实现 Trie (前缀树)]] | [54-实现 Trie (前缀树)](图论/54-实现 Trie (前缀树).md) |
|
||||
|
||||
---
|
||||
|
||||
### 回溯
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 55 | [全排列](https://leetcode.cn/problems/permutations/) | 🟡 中等 | [[回溯/55-全排列]] |
|
||||
| 56 | [子集](https://leetcode.cn/problems/subsets/) | 🟡 中等 | [[回溯/56-子集]] |
|
||||
| 57 | [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | 🟡 中等 | [[回溯/57-电话号码的字母组合]] |
|
||||
| 58 | [组合总和](https://leetcode.cn/problems/combination-sum/) | 🟡 中等 | [[回溯/58-组合总和]] |
|
||||
| 59 | [括号生成](https://leetcode.cn/problems/generate-parentheses/) | 🟡 中等 | [[回溯/59-括号生成]] |
|
||||
| 60 | [单词搜索](https://leetcode.cn/problems/word-search/) | 🟡 中等 | [[回溯/60-单词搜索]] |
|
||||
| 61 | [分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) | 🟡 中等 | [[回溯/61-分割回文串]] |
|
||||
| 62 | [N 皇后](https://leetcode.cn/problems/n-queens/) | 🔴 困难 | [[回溯/62-N 皇后]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 55 | [全排列](https://leetcode.cn/problems/permutations/) | 🟡 中等 | [[回溯/55-全排列]] | [55-全排列](回溯/55-全排列.md) |
|
||||
| 56 | [子集](https://leetcode.cn/problems/subsets/) | 🟡 中等 | [[回溯/56-子集]] | [56-子集](回溯/56-子集.md) |
|
||||
| 57 | [电话号码的字母组合](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) | 🟡 中等 | [[回溯/57-电话号码的字母组合]] | [57-电话号码的字母组合](回溯/57-电话号码的字母组合.md) |
|
||||
| 58 | [组合总和](https://leetcode.cn/problems/combination-sum/) | 🟡 中等 | [[回溯/58-组合总和]] | [58-组合总和](回溯/58-组合总和.md) |
|
||||
| 59 | [括号生成](https://leetcode.cn/problems/generate-parentheses/) | 🟡 中等 | [[回溯/59-括号生成]] | [59-括号生成](回溯/59-括号生成.md) |
|
||||
| 60 | [单词搜索](https://leetcode.cn/problems/word-search/) | 🟡 中等 | [[回溯/60-单词搜索]] | [60-单词搜索](回溯/60-单词搜索.md) |
|
||||
| 61 | [分割回文串](https://leetcode.cn/problems/palindrome-partitioning/) | 🟡 中等 | [[回溯/61-分割回文串]] | [61-分割回文串](回溯/61-分割回文串.md) |
|
||||
| 62 | [N 皇后](https://leetcode.cn/problems/n-queens/) | 🔴 困难 | [[回溯/62-N 皇后]] | [62-N 皇后](回溯/62-N 皇后.md) |
|
||||
|
||||
---
|
||||
|
||||
### 二分查找
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 63 | [搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | 🟢 简单 | [[二分查找/63-搜索插入位置]] |
|
||||
| 64 | [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | 🟡 中等 | [[二分查找/64-搜索二维矩阵]] |
|
||||
| 65 | [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | 🟡 中等 | [[二分查找/65-在排序数组中查找元素的第一个和最后一个位置]] |
|
||||
| 66 | [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | 🟡 中等 | [[二分查找/66-搜索旋转排序数组]] |
|
||||
| 67 | [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | 🟡 中等 | [[二分查找/67-寻找旋转排序数组中的最小值]] |
|
||||
| 68 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | 🔴 困难 | [[二分查找/68-寻找两个正序数组的中位数]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 63 | [搜索插入位置](https://leetcode.cn/problems/search-insert-position/) | 🟢 简单 | [[二分查找/63-搜索插入位置]] | [63-搜索插入位置](二分查找/63-搜索插入位置.md) |
|
||||
| 64 | [搜索二维矩阵](https://leetcode.cn/problems/search-a-2d-matrix/) | 🟡 中等 | [[二分查找/64-搜索二维矩阵]] | [64-搜索二维矩阵](二分查找/64-搜索二维矩阵.md) |
|
||||
| 65 | [在排序数组中查找元素的第一个和最后一个位置](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/) | 🟡 中等 | [[二分查找/65-在排序数组中查找元素的第一个和最后一个位置]] | [65-在排序数组中查找元素的第一个和最后一个位置](二分查找/65-在排序数组中查找元素的第一个和最后一个位置.md) |
|
||||
| 66 | [搜索旋转排序数组](https://leetcode.cn/problems/search-in-rotated-sorted-array/) | 🟡 中等 | [[二分查找/66-搜索旋转排序数组]] | [66-搜索旋转排序数组](二分查找/66-搜索旋转排序数组.md) |
|
||||
| 67 | [寻找旋转排序数组中的最小值](https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/) | 🟡 中等 | [[二分查找/67-寻找旋转排序数组中的最小值]] | [67-寻找旋转排序数组中的最小值](二分查找/67-寻找旋转排序数组中的最小值.md) |
|
||||
| 68 | [寻找两个正序数组的中位数](https://leetcode.cn/problems/median-of-two-sorted-arrays/) | 🔴 困难 | [[二分查找/68-寻找两个正序数组的中位数]] | [68-寻找两个正序数组的中位数](二分查找/68-寻找两个正序数组的中位数.md) |
|
||||
|
||||
---
|
||||
|
||||
### 栈
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 69 | [有效的括号](https://leetcode.cn/problems/valid-parentheses/) | 🟢 简单 | [[栈/69-有效的括号]] |
|
||||
| 70 | [最小栈](https://leetcode.cn/problems/min-stack/) | 🟡 中等 | [[栈/70-最小栈]] |
|
||||
| 71 | [字符串解码](https://leetcode.cn/problems/decode-string/) | 🟡 中等 | [[栈/71-字符串解码]] |
|
||||
| 72 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | 🟡 中等 | [[栈/72-每日温度]] |
|
||||
| 73 | [柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) | 🔴 困难 | [[栈/73-柱状图中最大的矩形]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 69 | [有效的括号](https://leetcode.cn/problems/valid-parentheses/) | 🟢 简单 | [[栈/69-有效的括号]] | [69-有效的括号](栈/69-有效的括号.md) |
|
||||
| 70 | [最小栈](https://leetcode.cn/problems/min-stack/) | 🟡 中等 | [[栈/70-最小栈]] | [70-最小栈](栈/70-最小栈.md) |
|
||||
| 71 | [字符串解码](https://leetcode.cn/problems/decode-string/) | 🟡 中等 | [[栈/71-字符串解码]] | [71-字符串解码](栈/71-字符串解码.md) |
|
||||
| 72 | [每日温度](https://leetcode.cn/problems/daily-temperatures/) | 🟡 中等 | [[栈/72-每日温度]] | [72-每日温度](栈/72-每日温度.md) |
|
||||
| 73 | [柱状图中最大的矩形](https://leetcode.cn/problems/largest-rectangle-in-histogram/) | 🔴 困难 | [[栈/73-柱状图中最大的矩形]] | [73-柱状图中最大的矩形](栈/73-柱状图中最大的矩形.md) |
|
||||
|
||||
---
|
||||
|
||||
### 堆
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 74 | [数组中的第 K 个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | 🟡 中等 | [[堆/74-数组中的第 K 个最大元素]] |
|
||||
| 75 | [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | 🟡 中等 | [[堆/75-前 K 个高频元素]] |
|
||||
| 76 | [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | 🔴 困难 | [[堆/76-数据流的中位数]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 74 | [数组中的第 K 个最大元素](https://leetcode.cn/problems/kth-largest-element-in-an-array/) | 🟡 中等 | [[堆/74-数组中的第 K 个最大元素]] | [74-数组中的第 K 个最大元素](堆/74-数组中的第 K 个最大元素.md) |
|
||||
| 75 | [前 K 个高频元素](https://leetcode.cn/problems/top-k-frequent-elements/) | 🟡 中等 | [[堆/75-前 K 个高频元素]] | [75-前 K 个高频元素](堆/75-前 K 个高频元素.md) |
|
||||
| 76 | [数据流的中位数](https://leetcode.cn/problems/find-median-from-data-stream/) | 🔴 困难 | [[堆/76-数据流的中位数]] | [76-数据流的中位数](堆/76-数据流的中位数.md) |
|
||||
|
||||
---
|
||||
|
||||
### 贪心算法
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 77 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | 🟢 简单 | [[贪心算法/77-买卖股票的最佳时机]] |
|
||||
| 78 | [跳跃游戏](https://leetcode.cn/problems/jump-game/) | 🟡 中等 | [[贪心算法/78-跳跃游戏]] |
|
||||
| 79 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | 🟡 中等 | [[贪心算法/79-跳跃游戏 II]] |
|
||||
| 80 | [划分字母区间](https://leetcode.cn/problems/partition-labels/) | 🟡 中等 | [[贪心算法/80-划分字母区间]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 77 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | 🟢 简单 | [[贪心算法/77-买卖股票的最佳时机]] | [77-买卖股票的最佳时机](贪心算法/77-买卖股票的最佳时机.md) |
|
||||
| 78 | [跳跃游戏](https://leetcode.cn/problems/jump-game/) | 🟡 中等 | [[贪心算法/78-跳跃游戏]] | [78-跳跃游戏](贪心算法/78-跳跃游戏.md) |
|
||||
| 79 | [跳跃游戏 II](https://leetcode.cn/problems/jump-game-ii/) | 🟡 中等 | [[贪心算法/79-跳跃游戏 II]] | [79-跳跃游戏 II](贪心算法/79-跳跃游戏 II.md) |
|
||||
| 80 | [划分字母区间](https://leetcode.cn/problems/partition-labels/) | 🟡 中等 | [[贪心算法/80-划分字母区间]] | [80-划分字母区间](贪心算法/80-划分字母区间.md) |
|
||||
|
||||
---
|
||||
|
||||
### 动态规划
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 81 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | 🟢 简单 | [[动态规划/81-爬楼梯]] |
|
||||
| 82 | [杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | 🟢 简单 | [[动态规划/82-杨辉三角]] |
|
||||
| 83 | [打家劫舍](https://leetcode.cn/problems/house-robber/) | 🟡 中等 | [[动态规划/83-打家劫舍]] |
|
||||
| 84 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | 🟡 中等 | [[动态规划/84-完全平方数]] |
|
||||
| 85 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | 🟡 中等 | [[动态规划/85-零钱兑换]] |
|
||||
| 86 | [单词拆分](https://leetcode.cn/problems/word-break/) | 🟡 中等 | [[动态规划/86-单词拆分]] |
|
||||
| 87 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | 🟡 中等 | [[动态规划/87-最长递增子序列]] |
|
||||
| 88 | [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | 🟡 中等 | [[动态规划/88-乘积最大子数组]] |
|
||||
| 89 | [分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | 🟡 中等 | [[动态规划/89-分割等和子集]] |
|
||||
| 90 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | 🔴 困难 | [[动态规划/90-最长有效括号]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 81 | [爬楼梯](https://leetcode.cn/problems/climbing-stairs/) | 🟢 简单 | [[动态规划/81-爬楼梯]] | [81-爬楼梯](动态规划/81-爬楼梯.md) |
|
||||
| 82 | [杨辉三角](https://leetcode.cn/problems/pascals-triangle/) | 🟢 简单 | [[动态规划/82-杨辉三角]] | [82-杨辉三角](动态规划/82-杨辉三角.md) |
|
||||
| 83 | [打家劫舍](https://leetcode.cn/problems/house-robber/) | 🟡 中等 | [[动态规划/83-打家劫舍]] | [83-打家劫舍](动态规划/83-打家劫舍.md) |
|
||||
| 84 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | 🟡 中等 | [[动态规划/84-完全平方数]] | [84-完全平方数](动态规划/84-完全平方数.md) |
|
||||
| 85 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | 🟡 中等 | [[动态规划/85-零钱兑换]] | [85-零钱兑换](动态规划/85-零钱兑换.md) |
|
||||
| 86 | [单词拆分](https://leetcode.cn/problems/word-break/) | 🟡 中等 | [[动态规划/86-单词拆分]] | [86-单词拆分](动态规划/86-单词拆分.md) |
|
||||
| 87 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | 🟡 中等 | [[动态规划/87-最长递增子序列]] | [87-最长递增子序列](动态规划/87-最长递增子序列.md) |
|
||||
| 88 | [乘积最大子数组](https://leetcode.cn/problems/maximum-product-subarray/) | 🟡 中等 | [[动态规划/88-乘积最大子数组]] | [88-乘积最大子数组](动态规划/88-乘积最大子数组.md) |
|
||||
| 89 | [分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/) | 🟡 中等 | [[动态规划/89-分割等和子集]] | [89-分割等和子集](动态规划/89-分割等和子集.md) |
|
||||
| 90 | [最长有效括号](https://leetcode.cn/problems/longest-valid-parentheses/) | 🔴 困难 | [[动态规划/90-最长有效括号]] | [90-最长有效括号](动态规划/90-最长有效括号.md) |
|
||||
|
||||
---
|
||||
|
||||
### 多维动态规划
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 91 | [不同路径](https://leetcode.cn/problems/unique-paths/) | 🟡 中等 | [[多维动态规划/91-不同路径]] |
|
||||
| 92 | [最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | 🟡 中等 | [[多维动态规划/92-最小路径和]] |
|
||||
| 93 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | 🟡 中等 | [[多维动态规划/93-最长回文子串]] |
|
||||
| 94 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | 🟡 中等 | [[多维动态规划/94-最长公共子序列]] |
|
||||
| 95 | [编辑距离](https://leetcode.cn/problems/edit-distance/) | 🟡 中等 | [[多维动态规划/95-编辑距离]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 91 | [不同路径](https://leetcode.cn/problems/unique-paths/) | 🟡 中等 | [[多维动态规划/91-不同路径]] | [91-不同路径](多维动态规划/91-不同路径.md) |
|
||||
| 92 | [最小路径和](https://leetcode.cn/problems/minimum-path-sum/) | 🟡 中等 | [[多维动态规划/92-最小路径和]] | [92-最小路径和](多维动态规划/92-最小路径和.md) |
|
||||
| 93 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | 🟡 中等 | [[多维动态规划/93-最长回文子串]] | [93-最长回文子串](多维动态规划/93-最长回文子串.md) |
|
||||
| 94 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | 🟡 中等 | [[多维动态规划/94-最长公共子序列]] | [94-最长公共子序列](多维动态规划/94-最长公共子序列.md) |
|
||||
| 95 | [编辑距离](https://leetcode.cn/problems/edit-distance/) | 🟡 中等 | [[多维动态规划/95-编辑距离]] | [95-编辑距离](多维动态规划/95-编辑距离.md) |
|
||||
|
||||
---
|
||||
|
||||
### 技巧
|
||||
|
||||
| # | 题目 | 难度 | 解答 |
|
||||
|---|------|:----:|------|
|
||||
| 96 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | 🟢 简单 | [[技巧/96-只出现一次的数字]] |
|
||||
| 97 | [多数元素](https://leetcode.cn/problems/majority-element/) | 🟢 简单 | [[技巧/97-多数元素]] |
|
||||
| 98 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | 🟡 中等 | [[技巧/98-颜色分类]] |
|
||||
| 99 | [下一个排列](https://leetcode.cn/problems/next-permutation/) | 🟡 中等 | [[技巧/99-下一个排列]] |
|
||||
| 100 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | 🟡 中等 | [[技巧/100-寻找重复数]] |
|
||||
| # | 题目 | 难度 | Obsidian | Gitea |
|
||||
|---|------|:----:|---------|-------|
|
||||
| 96 | [只出现一次的数字](https://leetcode.cn/problems/single-number/) | 🟢 简单 | [[技巧/96-只出现一次的数字]] | [96-只出现一次的数字](技巧/96-只出现一次的数字.md) |
|
||||
| 97 | [多数元素](https://leetcode.cn/problems/majority-element/) | 🟢 简单 | [[技巧/97-多数元素]] | [97-多数元素](技巧/97-多数元素.md) |
|
||||
| 98 | [颜色分类](https://leetcode.cn/problems/sort-colors/) | 🟡 中等 | [[技巧/98-颜色分类]] | [98-颜色分类](技巧/98-颜色分类.md) |
|
||||
| 99 | [下一个排列](https://leetcode.cn/problems/next-permutation/) | 🟡 中等 | [[技巧/99-下一个排列]] | [99-下一个排列](技巧/99-下一个排列.md) |
|
||||
| 100 | [寻找重复数](https://leetcode.cn/problems/find-the-duplicate-number/) | 🟡 中等 | [[技巧/100-寻找重复数]] | [100-寻找重复数](技巧/100-寻找重复数.md) |
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
tags: [面试, 笔试, 微派, 算法, 计算机网络, 操作系统, 数据库]
|
||||
create time: 2026-05-16 14:30
|
||||
---
|
||||
|
||||
# 微派 Test1
|
||||
|
||||
> [!info] 考试信息
|
||||
> - **时长**: 一个半小时
|
||||
> - **题型**: 2道编程题 + 10道单选题
|
||||
> - **模式**: ACM模式,无测试用例,主要看代码逻辑
|
||||
> - **难度**: 中等
|
||||
|
||||
## 编程题
|
||||
|
||||
| # | 题目 | 核心考点 | 详细解析 |
|
||||
|---|------|---------|---------|
|
||||
| 1 | 最长严格递增子序列(返回具体元素) | DP + 贪心 + 二分 | [[微派 Test1/01-LIS返回子序列.md]] |
|
||||
| 2 | 删除链表倒数第n个节点 | 双指针 / 虚拟头结点 | [[微派 Test1/02-删除链表倒数第N个节点.md]] |
|
||||
|
||||
## 单选题
|
||||
|
||||
> [!tip] 💡 点击每题可查看完整解析
|
||||
|
||||
| # | 考点 | 难度 | 详细解析 |
|
||||
|---|------|------|---------|
|
||||
| 1 | MySQL 索引下推 (ICP) | ⭐⭐ | [[微派 Test1/03-单选题1-索引下推.md]] |
|
||||
| 2 | 二叉树后序遍历推导 | ⭐⭐ | [[微派 Test1/04-单选题2-后序遍历.md]] |
|
||||
| 3 | Agent 依赖关系 | ⭐ | [[微派 Test1/05-单选题3-Agent依赖.md]] |
|
||||
| 4 | 线程资源隔离 | ⭐⭐ | [[微派 Test1/06-单选题4-线程资源.md]] |
|
||||
| 5 | TCP 滑动窗口 | ⭐⭐⭐ | [[微派 Test1/07-单选题5-TCP滑动窗口.md]] |
|
||||
| 6 | Go 参数传递机制 | ⭐⭐⭐ | [[微派 Test1/08-单选题6-参数传递.md]] |
|
||||
| 7 | BFS vs DFS | ⭐⭐ | [[微派 Test1/09-单选题7-图遍历.md]] |
|
||||
| 8 | 哈希桶与扩容 | ⭐⭐ | [[微派 Test1/10-单选题8-哈希桶.md]] |
|
||||
| 9 | HTTP Cache-Control | ⭐⭐ | [[微派 Test1/11-单选题9-Http响应头.md]] |
|
||||
| 10 | BST 后序遍历判定 | ⭐⭐⭐ | [[微派 Test1/12-单选题10-树后序遍历.md]] |
|
||||
@@ -1,286 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, DP, 贪心, 二分查找, Go]
|
||||
create time: 2026-05-16 14:35
|
||||
---
|
||||
|
||||
# 01 - 最长严格递增子序列(返回子序列)
|
||||
|
||||
## 题面
|
||||
|
||||
**输入**: 一个整数数组 `nums`
|
||||
|
||||
**输出**: 一个切片,表示 nums 的**最长严格递增子序列(Longest Increasing Subsequence)**的具体元素
|
||||
|
||||
**要求**:
|
||||
- 子序列不要求连续,但必须保持原顺序
|
||||
- **严格递增**:后一项必须**大于**前一项(不能等于)
|
||||
- ACM 模式:从 stdin 读取,stdout 输出结果
|
||||
- 如有多个答案,返回任意一个即可
|
||||
|
||||
**示例 1**:
|
||||
```
|
||||
输入: [10, 9, 2, 5, 3, 7, 101, 18]
|
||||
输出: [2, 3, 7, 101] (或 [2, 3, 7, 18])
|
||||
```
|
||||
|
||||
**示例 2**:
|
||||
```
|
||||
输入: [0, 1, 0, 3, 2, 3]
|
||||
输出: [0, 1, 2, 3]
|
||||
```
|
||||
|
||||
**示例 3** (严格递增):
|
||||
```
|
||||
输入: [3, 3, 3, 3]
|
||||
输出: [3] (长度为 1,因为相等不算递增)
|
||||
```
|
||||
|
||||
> [!question] 💡 思考一下
|
||||
> 如果题目要求的是**非递减**子序列(可以等于),解法需要怎么调整?
|
||||
|
||||
---
|
||||
|
||||
## 思路
|
||||
|
||||
### 方法一:O(n²) 动态规划(基础)
|
||||
|
||||
定义 `dp[i] = 以 nums[i] 结尾的最长递增子序列长度`。
|
||||
|
||||
转移方程:
|
||||
|
||||
$$dp[i] = \max \{ dp[j] \} + 1, \quad j < i,\; nums[j] < nums[i]$$
|
||||
|
||||
回溯时,从 dp 值最大的位置倒推,找到前驱元素即可恢复完整子序列。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["nums = [10, 9, 2, 5, 3, 7, 101, 18]"] --> B["计算 dp[i]"]
|
||||
B --> C["dp = [1, 1, 1, 2, 2, 3, 4, 4]"]
|
||||
C --> D["找到最大值的最后一个位置"]
|
||||
D --> E["倒推前驱还原子序列"]
|
||||
E --> F["[2, 3, 7, 101] 或 [2, 3, 7, 18]"]
|
||||
```
|
||||
|
||||
**优点**: 思路直观,容易实现
|
||||
**缺点**: O(n²) 时间复杂度,大数据量会超时
|
||||
|
||||
---
|
||||
|
||||
### 方法二:O(n log n) 贪心 + 二分 + 路径回溯(推荐)⭐
|
||||
|
||||
这是本题的**核心考点**。很多人只记得求长度的 O(n log n) 解法,但本题要求**返回具体序列**,需要额外技巧。
|
||||
|
||||
#### 核心数据结构
|
||||
|
||||
维护两个数组:
|
||||
|
||||
| 数组 | 含义 |
|
||||
|------|------|
|
||||
| `tails[k]` | 长度为 k+1 的所有递增子序列中,**最小尾部元素**的值 |
|
||||
| `parent[i]` | 以 `nums[i]` 结尾的 LIS 中,`nums[i]` 的**前驱索引** |
|
||||
|
||||
#### 关键洞察 🔑
|
||||
|
||||
> [!warning] ⚠️ 易错点
|
||||
> `tails` 数组**不一定**是真实的子序列!它只是帮助我们高效找到更长的子序列。真正恢复子序列要靠 `parent` 数组记录的路径。
|
||||
|
||||
#### 算法步骤
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% 定义所有节点
|
||||
A["遍历每个元素 nums[i]"]
|
||||
B{"在 tails 中<br/>二分查找"}
|
||||
C["追加到 tails 末尾<br/>更新 parent[i]=prevIdx"]
|
||||
D["用 nums[i] 替换<br/>tails[j]<br/>parent[i]=j-1<br/>对应的前驱索引"]
|
||||
E["i++"]
|
||||
F{"是否遍历完?"}
|
||||
G["从 tails 末尾开始<br/>回溯 parent"]
|
||||
H["得到逆序的子序列"]
|
||||
I["翻转得到正序"]
|
||||
|
||||
%% 连线
|
||||
A --> B
|
||||
B -- "nums[i] > 所有 tails" --> C
|
||||
B -- "tails[j-1] < nums[i]" --> D
|
||||
C --> E
|
||||
D --> E
|
||||
E --> F
|
||||
F -- "否" --> A
|
||||
F -- "是" --> G
|
||||
G --> H
|
||||
H --> I
|
||||
```
|
||||
|
||||
**细节**: 为了正确设置 `parent[i]`,我们还需要一个数组 `pos[k]` 记录长度为 k 的子序列当前尾元素的索引。
|
||||
|
||||
#### 完整流程演示
|
||||
|
||||
```
|
||||
nums = [10, 9, 2, 5, 3, 7, 101, 18]
|
||||
|
||||
i=0: nums[0]=10, tails=[], pos=[]
|
||||
tails=[10], pos=[0], parent[0]=-1
|
||||
|
||||
i=1: nums[1]=9, 9<10, 替换 tails[0]
|
||||
tails=[9], pos=[1], parent[1]=-1
|
||||
|
||||
i=2: nums[2]=2, 2<9, 替换 tails[0]
|
||||
tails=[2], pos=[2], parent[2]=-1
|
||||
|
||||
i=3: nums[3]=5, 5>2, 追加
|
||||
tails=[2,5], pos=[2,3], parent[3]=2
|
||||
|
||||
i=4: nums[4]=3, 2<3≤5, 替换 tails[1]
|
||||
tails=[2,3], pos=[2,4], parent[4]=2
|
||||
|
||||
i=5: nums[5]=7, 7>3, 追加
|
||||
tails=[2,3,7], pos=[2,4,5], parent[5]=4
|
||||
|
||||
i=6: nums[6]=101, 101>7, 追加
|
||||
tails=[2,3,7,101], pos=[2,4,5,6], parent[6]=5
|
||||
|
||||
i=7: nums[7]=18, 7<18≤101, 替换 tails[3]
|
||||
tails=[2,3,7,18], pos=[2,4,5,7], parent[7]=5
|
||||
|
||||
LIS 长度 = 4, 从 pos[3]=7 开始回溯:
|
||||
nums[7]=18 → parent[7]=5 → nums[5]=7 → parent[5]=4 → nums[4]=3 → parent[4]=2 → nums[2]=2 → parent[2]=-1
|
||||
逆序: [18, 7, 3, 2] → 翻转 → [2, 3, 7, 18] ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 代码提示
|
||||
|
||||
```go
|
||||
// 1. 初始化 tails []int, pos []int, parent []int{-1}
|
||||
// 2. 遍历 i := 0 to n-1:
|
||||
// - 二分搜索 tails,找到第一个 >= nums[i] 的位置 j
|
||||
// - if j == len(tails): 追加,否则替换 tails[j]
|
||||
// - 更新 pos[j] = i, parent[i] = pos[j-1] (if j > 0)
|
||||
// 3. 从 pos[len(tails)-1] 沿 parent 倒推
|
||||
// 4. 反转结果
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技巧
|
||||
|
||||
> [!tip] 小技巧: 二分搜索的使用
|
||||
> `sort.SearchInts` 返回的是**第一个 >= target** 的位置,正好符合我们的需求。如果用标准库 `lower_bound` 语义,找的是 `>=`;如果要处理**非严格递增**(允许等于),就改为找 `>`。
|
||||
|
||||
> [!note] ACM 模式注意事项
|
||||
> - 用 `bufio.Scanner` 读行比 `fmt.Scan` 更高效
|
||||
> - 输出格式通常是用空格分隔的数字
|
||||
> - 记得处理空数组边界情况 `n == 0`
|
||||
|
||||
---
|
||||
|
||||
## 代码
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
if !scanner.Scan() {
|
||||
return
|
||||
}
|
||||
|
||||
// 解析输入: "10 9 2 5 3 7 101 18"
|
||||
parts := strings.Fields(scanner.Text())
|
||||
n := len(parts)
|
||||
if n == 0 {
|
||||
fmt.Println("[]")
|
||||
return
|
||||
}
|
||||
|
||||
nums := make([]int, n)
|
||||
for i, p := range parts {
|
||||
v, _ := strconv.Atoi(p)
|
||||
nums[i] = v
|
||||
}
|
||||
|
||||
// LIS 返回具体子序列
|
||||
result := longestIncreasingSubsequence(nums)
|
||||
|
||||
// 输出结果
|
||||
fmt.Print("[")
|
||||
for i, v := range result {
|
||||
if i > 0 {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
fmt.Print(v)
|
||||
}
|
||||
fmt.Println("]")
|
||||
}
|
||||
|
||||
func longestIncreasingSubsequence(nums []int) []int {
|
||||
n := len(nums)
|
||||
if n == 0 {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
// tails[k] = 长度为 k+1 的递增子序列的最小尾部值
|
||||
tails := make([]int, 0, n)
|
||||
// pos[k] = 该尾部值在原数组中的索引
|
||||
pos := make([]int, n)
|
||||
// parent[i] = 以 nums[i] 结尾的 LIS 中,前一个元素的索引
|
||||
parent := make([]int, n)
|
||||
for i := range parent {
|
||||
parent[i] = -1
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
// 二分查找: 在 tails 中找第一个 >= nums[i] 的位置
|
||||
j := lowerBound(tails, nums[i])
|
||||
|
||||
if j == len(tails) {
|
||||
// nums[i] 可以接在当前最长子序列后面
|
||||
tails = append(tails, nums[i])
|
||||
} else {
|
||||
// 用较小的 nums[i] 替换 tails[j]
|
||||
tails[j] = nums[i]
|
||||
}
|
||||
pos[j] = i // 记录长度为 j+1 的子序列尾部索引
|
||||
|
||||
if j > 0 {
|
||||
parent[i] = pos[j-1] // 前驱是长度为 j 的子序列尾部
|
||||
}
|
||||
}
|
||||
|
||||
// 从最长子序列的尾部开始,沿 parent 回溯
|
||||
length := len(tails)
|
||||
result := make([]int, length)
|
||||
result[length-1] = nums[pos[length-1]]
|
||||
k := pos[length-1]
|
||||
for i := length - 2; i >= 0; i-- {
|
||||
k = parent[k]
|
||||
result[i] = nums[k]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// lowerBound 返回第一个 >= target 的位置
|
||||
func lowerBound(a []int, target int) int {
|
||||
left, right := 0, len(a)
|
||||
for left < right {
|
||||
mid := left + (right-left)/2
|
||||
if a[mid] < target {
|
||||
left = mid + 1
|
||||
} else {
|
||||
right = mid
|
||||
}
|
||||
}
|
||||
return left
|
||||
}
|
||||
```
|
||||
@@ -1,279 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, 链表, 双指针, Go]
|
||||
create time: 2026-05-16 14:40
|
||||
---
|
||||
|
||||
# 02 - 删除链表倒数第 n 个节点
|
||||
|
||||
## 题面
|
||||
|
||||
**输入**:
|
||||
1. 一个单向链表的头结点 `head`
|
||||
2. 整数 `n`(`1 ≤ n ≤ 链表长度`)
|
||||
|
||||
**输出**: 删除倒数第 n 个节点后的链表头结点
|
||||
|
||||
**要求**:
|
||||
- ACM 模式:从 stdin 读取,stdout 输出结果
|
||||
- **一次遍历**完成(O(1) 额外空间)
|
||||
- 被删除节点一定存在
|
||||
|
||||
**示例 1**:
|
||||
```
|
||||
输入: head = [1, 2, 3, 4, 5], n = 2
|
||||
输出: [1, 2, 3, 5]
|
||||
解释: 倒数第 2 个是 4,删除后得到 1->2->3->5
|
||||
```
|
||||
|
||||
**示例 2**:
|
||||
```
|
||||
输入: head = [1], n = 1
|
||||
输出: []
|
||||
解释: 只有一个节点,删除它后链表为空
|
||||
```
|
||||
|
||||
**示例 3**:
|
||||
```
|
||||
输入: head = [1, 2], n = 1
|
||||
输出: [1]
|
||||
解释: 倒数第 1 个(即尾节点 2),删除后剩下 1
|
||||
```
|
||||
|
||||
> [!question] 💡 思考一下
|
||||
> 如果题目说"只允许遍历一次",为什么不能先走一遍求长度再走一遍删除?
|
||||
|
||||
---
|
||||
|
||||
## 思路
|
||||
|
||||
### 方法一:快慢指针(双指针)⭐ 最优
|
||||
|
||||
#### 核心思想
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["dummy -> 1 -> 2 -> 3 -> 4 -> 5<br/>n = 2"] --> B["fast 和 slow 都从 dummy 出发"]
|
||||
B --> C["第一步: fast 先走 n+1 = 3 步"]
|
||||
C --> D["此时 fast=dummy, slow=dummy<br/>fast 在空位, slow 在 dummy"]
|
||||
D --> E["第二步: 一起走直到 fast == nil"]
|
||||
E --> F["slow 停在 3 的位置"]
|
||||
F --> G["slow.Next = slow.Next.Next<br/>跳过节点 4"]
|
||||
G --> H["删除完成: 1->2->3->5"]
|
||||
```
|
||||
|
||||
#### 关键步骤
|
||||
|
||||
1. **创建虚拟头结点 `dummy`**,指向 `head`
|
||||
- 为什么要 dummy?因为**可能删除头结点本身**(如示例 2),用 dummy 可以避免特殊判断。
|
||||
|
||||
2. **快指针 `fast` 先走 n+1 步**
|
||||
- 走到 n+1 而不是 n,是为了让慢指针最终停在**要删除节点的前驱**。
|
||||
|
||||
3. **快慢指针同步前进**,直到 `fast == nil`
|
||||
|
||||
4. **执行删除**: `slow.Next = slow.Next.Next`
|
||||
|
||||
#### 为什么是 n+1 步?
|
||||
|
||||
```
|
||||
链表: dummy → 1 → 2 → 3 → 4 → 5
|
||||
↑
|
||||
删除倒数第 2 个(节点 4)
|
||||
|
||||
fast 先走 3 步 (n+1):
|
||||
dummy → 1 → 2 → 3 → 4 → 5
|
||||
^ ^ ^
|
||||
dummy dummy(fast) ...继续...
|
||||
|
||||
不对,让我重新演示:
|
||||
dummy → 1 → 2 → 3 → 4 → 5 → nil
|
||||
^
|
||||
fast 走1步 → 1
|
||||
fast 走2步 → 2
|
||||
fast 走3步 → 3 (n+1=3 步)
|
||||
|
||||
现在 fast/slow 同时走:
|
||||
slow→1, fast→4 (step 1)
|
||||
slow→2, fast→5 (step 2)
|
||||
slow→3, fast→nil (step 3, 停止)
|
||||
|
||||
slow 停在 3,slow.Next = 4(要删除的节点)✓
|
||||
```
|
||||
|
||||
> [!tip] 规律总结
|
||||
> - 快指针先走 `n+1` 步
|
||||
> - 当快指针到达 `nil` 时,慢指针恰好停在被删节点的**前驱**
|
||||
> - 时间复杂度 O(n),空间复杂度 O(1)
|
||||
|
||||
---
|
||||
|
||||
### 方法二:两次遍历(备选)
|
||||
|
||||
虽然题目通常要求一次遍历,但了解这个方法有助于面试中展示全面性:
|
||||
|
||||
1. 第一次遍历计算链表长度 L
|
||||
2. 第二次遍历走到正数第 `(L-n)` 个节点,执行删除
|
||||
|
||||
时间 O(n),空间 O(1),但需要**两次**遍历。
|
||||
|
||||
---
|
||||
|
||||
### 方法三:栈(备选)
|
||||
|
||||
1. 将所有节点入栈
|
||||
2. 弹出 n 次,第 n 次弹出的就是要删除的节点
|
||||
3. 栈顶元素即为前驱节点,执行删除
|
||||
|
||||
时间 O(n),空间 O(n)(不满足 O(1) 空间要求)。
|
||||
|
||||
---
|
||||
|
||||
## 代码提示
|
||||
|
||||
```go
|
||||
// 1. 定义 ListNode 结构体
|
||||
// 2. 创建 dummy := &ListNode{Next: head}
|
||||
// 3. fast, slow := dummy, dummy
|
||||
// 4. for i := 0; i <= n; i++ { fast = fast.Next } // 先走 n+1 步
|
||||
// 5. for fast != nil { fast = fast.Next; slow = slow.Next }
|
||||
// 6. slow.Next = slow.Next.Next // 删除
|
||||
// 7. return dummy.Next
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技巧
|
||||
|
||||
> [!note] ACM 模式的辅助函数
|
||||
> 在 ACM 模式下,通常需要写 helper 函数来将数组转为链表,以及将链表输出为数组格式。这些虽然不是算法核心,但**必不可少**。
|
||||
|
||||
```go
|
||||
// 常用模板:数组转链表
|
||||
func arrToList(arr []int) *ListNode {
|
||||
dummy := &ListNode{}
|
||||
cur := dummy
|
||||
for _, v := range arr {
|
||||
cur.Next = &ListNode{Val: v}
|
||||
cur = cur.Next
|
||||
}
|
||||
return dummy.Next
|
||||
}
|
||||
|
||||
// 常用模板:链表转数组
|
||||
func listToArray(head *ListNode) []int {
|
||||
var result []int
|
||||
for head != nil {
|
||||
result = append(result, head.Val)
|
||||
head = head.Next
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
> [!warning] ⚠️ 边界检查
|
||||
> 删除首节点的情况由 `dummy` 自动处理,不需要 `if head == nil` 判断。但要注意 `n` 超过链表长度的情况——题目保证合法时可跳过。
|
||||
|
||||
---
|
||||
|
||||
## 代码
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ListNode 单链表节点定义
|
||||
type ListNode struct {
|
||||
Val int
|
||||
Next *ListNode
|
||||
}
|
||||
|
||||
func main() {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
|
||||
// 第一行: 数组形式的链表 "1 2 3 4 5"
|
||||
if !scanner.Scan() {
|
||||
return
|
||||
}
|
||||
parts := strings.Fields(scanner.Text())
|
||||
n := len(parts)
|
||||
|
||||
// 第二行: 目标 n
|
||||
if !scanner.Scan() {
|
||||
return
|
||||
}
|
||||
removeN, _ := strconv.Atoi(strings.TrimSpace(scanner.Text()))
|
||||
|
||||
// 数组转链表
|
||||
head := arrToList(parts)
|
||||
|
||||
// 删除倒数第 n 个节点
|
||||
newHead := removeNthFromEnd(head, removeN)
|
||||
|
||||
// 输出: 逗号分隔或空格分隔均可
|
||||
output := listToArray(newHead)
|
||||
if len(output) == 0 {
|
||||
fmt.Println("[]")
|
||||
} else {
|
||||
fmt.Print("[")
|
||||
for i, v := range output {
|
||||
if i > 0 {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
fmt.Print(v)
|
||||
}
|
||||
fmt.Println("]")
|
||||
}
|
||||
}
|
||||
|
||||
// removeNthFromEnd 一次遍历删除倒数第 n 个节点
|
||||
func removeNthFromEnd(head *ListNode, n int) *ListNode {
|
||||
// 创建虚拟头结点,简化头节点删除的处理
|
||||
dummy := &ListNode{Next: head}
|
||||
fast, slow := dummy, dummy
|
||||
|
||||
// 快指针先走 n+1 步
|
||||
for i := 0; i <= n; i++ {
|
||||
fast = fast.Next
|
||||
}
|
||||
|
||||
// 快慢指针同步前进,直到 fast 到达末尾
|
||||
for fast != nil {
|
||||
fast = fast.Next
|
||||
slow = slow.Next
|
||||
}
|
||||
|
||||
// slow 现在停在要删除节点的前驱
|
||||
slow.Next = slow.Next.Next
|
||||
|
||||
return dummy.Next
|
||||
}
|
||||
|
||||
// arrToList 将字符串数组转换为链表
|
||||
func arrToList(parts []string) *ListNode {
|
||||
dummy := &ListNode{}
|
||||
cur := dummy
|
||||
for _, p := range parts {
|
||||
v, _ := strconv.Atoi(p)
|
||||
cur.Next = &ListNode{Val: v}
|
||||
cur = cur.Next
|
||||
}
|
||||
return dummy.Next
|
||||
}
|
||||
|
||||
// listToArray 将链表转换为切片
|
||||
func listToArray(head *ListNode) []int {
|
||||
var result []int
|
||||
for head != nil {
|
||||
result = append(result, head.Val)
|
||||
head = head.Next
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, MySQL, 索引, 数据库]
|
||||
create time: 2026-05-16 14:45
|
||||
---
|
||||
|
||||
# 03 - 单选题 1:索引下推(Index Condition Pushdown)
|
||||
|
||||
## 题目
|
||||
|
||||
关于 MySQL InnoDB 存储引擎中的**索引下推(Index Condition Pushdown, ICP)**优化,以下说法**正确**的是:
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | 索引下推可以将条件判断从存储引擎层下推到 CPU 层执行 |
|
||||
| B | 索引下推适用于 ALL(全表扫描)类型的查询 |
|
||||
| C | 使用索引下推可以减少存储引擎访问表的次数(回表次数) |
|
||||
| D | 索引下推在所有情况下都能提升查询性能 |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**C**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### 什么是索引下推?
|
||||
|
||||
在优化器确定了访问数据的读策略(即选择了哪些索引)之后、读取数据之前,如果 WHERE 子句中还有可以利用索引中字段来评估的条件,则把这些条件的评估操作**下推给存储引擎层**执行,这个技术就叫索引下推。
|
||||
|
||||
#### 为什么需要索引下推?(问题背景)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph IC["无 ICP — 传统方式"]
|
||||
A[二级索引扫描到匹配的行] --> B["回表到聚簇索引获取完整行"]
|
||||
B --> C["在 Server 层检查剩余条件"]
|
||||
C --> D{"满足条件?"}
|
||||
D -->|"否"| E[丢弃该行]
|
||||
D -->|"是"| F["返回结果"]
|
||||
end
|
||||
|
||||
subgraph IX["有 ICP — 优化后"]
|
||||
G[二级索引扫描到匹配的行] --> H["在索引层直接检查剩余条件"]
|
||||
H --> I{"满足条件?"}
|
||||
I -->|"否"| J["跳过回表"]
|
||||
I -->|"是"| K["回表到聚簇索引"]
|
||||
K --> L["Server 层再确认"]
|
||||
L --> M["返回结果"]
|
||||
end
|
||||
|
||||
IC -.->|节省回表| IX
|
||||
```
|
||||
|
||||
**没有 ICP 时**:
|
||||
1. 二级索引只过滤第一个索引列(比如 `name = 'Alice'`)
|
||||
2. **所有**匹配的行都要**回表**到聚簇索引取完整记录
|
||||
3. Server 层再检查其他条件(如 `age > 25`)
|
||||
4. 不满足条件的行被丢弃——但**回表已经发生了**
|
||||
|
||||
**有了 ICP 后**:
|
||||
1. 二级索引不仅过滤第一个列,还**在索引层尝试过滤其余条件**
|
||||
2. 不满足索引列上的条件的行,**直接跳过回表**
|
||||
3. 只有同时满足所有索引列条件的行才回表
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
| 选项 | 正误 | 原因 |
|
||||
|------|------|------|
|
||||
| A | ❌ | 反了!ICP 是将条件从**Server 层**(CPU 层)下推到**存储引擎层**。应该是"上推"或题目说反了方向 |
|
||||
| B | ❌ | ICP 只在**使用二级索引**的查询中生效,ALL(全表扫描)不涉及索引,自然没有 ICP |
|
||||
| C | ✅ | **正确!**这是 ICP 的核心价值——减少不必要的回表操作 |
|
||||
| D | ❌ | ICP 需要索引中包含所有用到的列才能发挥效果,而且当回表后的额外过滤代价很低时,收益有限 |
|
||||
|
||||
#### 适用场景
|
||||
|
||||
```sql
|
||||
-- 复合索引 (name, age),ICP 可以发挥作用
|
||||
SELECT * FROM users
|
||||
WHERE name LIKE 'A%' AND age > 25;
|
||||
|
||||
-- 此时二级索引能过滤 name 和 age,减少回表
|
||||
```
|
||||
|
||||
> [!tip] 一句话总结
|
||||
> 索引下推的本质:把能尽早拒绝的行挡在门外,避免无效的回表开销。
|
||||
|
||||
</details>
|
||||
@@ -1,119 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, 树, 遍历, 数据结构]
|
||||
create time: 2026-05-16 14:50
|
||||
---
|
||||
|
||||
# 04 - 单选题 2:二叉树后序遍历
|
||||
|
||||
## 题目
|
||||
|
||||
已知一棵**二叉树**的**前序遍历**为 `[A, B, D, E, C, F]`,**中序遍历**为 `[D, B, E, A, C, F]`。则该树的**后序遍历**为:
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | `[D, E, B, F, C, A]` |
|
||||
| B | `[D, B, E, F, C, A]` |
|
||||
| C | `[E, D, B, F, C, A]` |
|
||||
| D | `[D, E, B, C, F, A]` |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**A**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### 解题思路
|
||||
|
||||
通过前序和中序遍历重建二叉树,然后手动推导后序遍历。
|
||||
|
||||
**核心性质**:
|
||||
- **前序**: `根 → 左子树 → 右子树`
|
||||
- **中序**: `左子树 → 根 → 右子树`
|
||||
- **后序**: `左子树 → 右子树 → 根`
|
||||
|
||||
#### 逐步重建
|
||||
|
||||
**第一步**:确定整棵树的根
|
||||
- 前序第一个 = `A` → **根节点是 A**
|
||||
- 在中序中找到 A:`[D, B, E, | A, C, F]`
|
||||
- 左子树(中序): `[D, B, E]`
|
||||
- 右子树(中序): `[C, F]`
|
||||
|
||||
```
|
||||
A
|
||||
/ \
|
||||
[D,B,E] [C,F]
|
||||
```
|
||||
|
||||
**第二步**:分解左子树
|
||||
- 前序中去掉根 A,剩余 `[B, D, E, C, F]`
|
||||
- 左子树长度 = 3,所以前序中左子树部分 = `[B, D, E]`
|
||||
- 左子树的前序第一个 = `B` → **左子树的根是 B**
|
||||
- 在中序 `[D, B, E]` 中找到 B:
|
||||
- B 的左子树(中序): `[D]`
|
||||
- B 的右子树(中序): `[E]`
|
||||
|
||||
```
|
||||
A
|
||||
/ \
|
||||
B ?
|
||||
/ \
|
||||
D E
|
||||
```
|
||||
|
||||
**第三步**:分解右子树
|
||||
- 前序中右子树部分 = `[C, F]`
|
||||
- 右子树的前序第一个 = `C` → **右子树的根是 C**
|
||||
- 在中序 `[C, F]` 中找到 C:
|
||||
- C 的左子树: `[]`(空)
|
||||
- C 的右子树: `[F]`
|
||||
|
||||
```
|
||||
A
|
||||
/ \
|
||||
B C
|
||||
/ \ \
|
||||
D E F
|
||||
```
|
||||
|
||||
**第四步**:写出后序遍历(左 → 右 → 根)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph LR["后序遍历时序"]
|
||||
S1["① D (B的左)"] --> S2["② E (B的右)"]
|
||||
S2 --> S3["③ B (左子树根)"]
|
||||
S3 --> S4["④ F (C的右)"]
|
||||
S4 --> S5["⑤ C (右子树根)"]
|
||||
S5 --> S6["⑥ A (整树根)"]
|
||||
end
|
||||
```
|
||||
|
||||
后序结果: **`[D, E, B, F, C, A]`**
|
||||
|
||||
#### 快速验证
|
||||
|
||||
检查每个选项的后序特点:
|
||||
- 后序最后一个元素一定是**根节点 A** → 所有选项都满足 ✓
|
||||
- 对于 B 的子树,D 和 E 应该是 B 的子节点,在后续中应在 B 之前出现
|
||||
- D 和 E 无子关系(单节点),可以任意顺序出现在 B 前 → `D, E` 正确
|
||||
|
||||
#### 面试延伸题
|
||||
|
||||
> [!question] 如果只给后序+中序?能唯一确定二叉树吗?
|
||||
>
|
||||
> **可以!** 原理类似:后序的最后一个元素是根,从中序划分左右子树,再递归处理。
|
||||
>
|
||||
> **只有前序+后序不能唯一确定**二叉树(除非是满二叉树)。
|
||||
|
||||
---
|
||||
|
||||
> [!tip] 记忆口诀
|
||||
> - 前序找根(第一个就是根)
|
||||
> - 中序分两边(根左边是左子树,右边是右子树)
|
||||
> - 后序最后根(根在最后出现)
|
||||
|
||||
</details>
|
||||
@@ -1,102 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, Agent, AI, 并发]
|
||||
create time: 2026-05-16 14:55
|
||||
---
|
||||
|
||||
# 05 - 单选题 3:Agent 依赖关系
|
||||
|
||||
## 题目
|
||||
|
||||
在一个多 Agent 协作系统中,存在以下依赖关系:
|
||||
- Agent B 需要调用 Agent A 的结果
|
||||
- Agent C 同时依赖于 Agent A 和 Agent B 的结果
|
||||
- Agent D 只依赖于 Agent A 的结果
|
||||
|
||||
下列说法**错误**的是:
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | Agent A 可以最先执行,无需等待其他 Agent |
|
||||
| B | Agent C 可以在 Agent B 完成后立即执行,无需等待 Agent A |
|
||||
| C | Agent D 可以和 Agent B 并行执行 |
|
||||
| D | 该依赖关系可以用有向无环图(DAG)来建模和执行调度 |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**B**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### 依赖关系图
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["Agent A"] --> B["Agent B"]
|
||||
A --> C["Agent C"]
|
||||
B --> C
|
||||
A --> D["Agent D"]
|
||||
```
|
||||
|
||||
也可以更清晰地用执行顺序图展示:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Stage1["Stage 1"]
|
||||
A["Agent A"]
|
||||
end
|
||||
|
||||
subgraph Stage2["Stage 2 (并行)"]
|
||||
B["Agent B"]
|
||||
D["Agent D"]
|
||||
end
|
||||
|
||||
subgraph Stage3["Stage 3"]
|
||||
C["Agent C"]
|
||||
end
|
||||
|
||||
A --> B
|
||||
A --> D
|
||||
B --> C
|
||||
A -.->|"也依赖A"| C
|
||||
```
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
| 选项 | 正误 | 原因 |
|
||||
|------|------|------|
|
||||
| A | ✅ 正确 | Agent A 没有任何前置依赖,可以最早执行 |
|
||||
| B | ❌ **错误(本题答案)** | Agent C 依赖于 A 和 B。虽然 B 完成了,但**还必须等 A**。实际上 A 在 B 之前就执行了,但从依赖角度,C 必须确认所有前置条件(A 和 B 都完成)才能开始 |
|
||||
| C | ✅ 正确 | Agent D 和 Agent B 都只依赖 A,A 完成后两者可以**并行执行** |
|
||||
| D | ✅ 正确 | 依赖关系是一个 DAG(没有循环),可以用拓扑排序确定执行顺序 |
|
||||
|
||||
#### DAG 调度执行流程
|
||||
|
||||
```
|
||||
时间线:
|
||||
───────────────────────────────→ t
|
||||
|
||||
t1: [✅ Agent A 执行中...]
|
||||
|
||||
t2: [⏳ Agent B 执行中] [⏳ Agent D 执行中]
|
||||
← 并行 (A已完成) ← 并行 (A已完成)
|
||||
|
||||
t3: [✅ Agent C 执行中]
|
||||
← B和A都已完成 ✓
|
||||
```
|
||||
|
||||
#### 核心知识点
|
||||
|
||||
1. **DAG(Directed Acyclic Graph)**: 有向无环图,是任务调度的基础模型
|
||||
2. **拓扑排序**: 对 DAG 进行线性排序,保证每个节点在其所有前驱之后出现
|
||||
3. **并行优化**: 互不依赖的任务可以并行执行以缩短总执行时间
|
||||
|
||||
> [!tip] 实际应用场景
|
||||
> 这种依赖模型广泛应用于: LangChain 的 LCEL Chain、Airflow DAG、GitHub Actions workflow、Kubeflow Pipelines。
|
||||
|
||||
> [!note] 如果改成循环依赖呢?
|
||||
> 若 "Agent A 也依赖 C",则形成环 → 无法执行 → 必须在框架层面检测并报错。
|
||||
|
||||
</details>
|
||||
@@ -1,102 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, 操作系统, 线程, 内存]
|
||||
create time: 2026-05-16 15:00
|
||||
---
|
||||
|
||||
# 06 - 单选题 4:线程的资源隔离
|
||||
|
||||
## 题目
|
||||
|
||||
在同一进程中创建的多个线程之间,以下哪一项资源是**不共享**的?
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | 堆(Heap)内存 |
|
||||
| B | 文件描述符表 |
|
||||
| C | 栈(Stack)空间 |
|
||||
| D | 信号处理函数 |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**C**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### 进程 vs 线程的资源划分
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
%%{init: {'theme':'base','themeVariables': {'tertiaryColor':'#f0f4f8'}}}%%
|
||||
P["🏢 进程地址空间"]
|
||||
|
||||
subgraph SH ["▸ 所有线程共享"]
|
||||
HEAP["📦 堆 Heap"]
|
||||
DATA["📄 数据段<br/>全局/静态变量"]
|
||||
BSS["📜 BSS 段<br/>未初始化全局变量"]
|
||||
CODE["🔧 代码段 .text"]
|
||||
FD["📋 文件描述符表"]
|
||||
SIG["⚙️ 信号处理函数"]
|
||||
CWD["🏠 当前工作目录"]
|
||||
end
|
||||
|
||||
subgraph PT ["▸ 每个线程独有"]
|
||||
ST1["📚 栈 Stack"]
|
||||
REG["🎛️ 寄存器集合"]
|
||||
PC["💻 程序计数器 PC"]
|
||||
TLS["🔒 线程本地存储 TLS"]
|
||||
ERRNO["🚩 错误号 errno"]
|
||||
end
|
||||
|
||||
SEP["━━━━━━━━ 分 隔 ━━━━━━━━"]
|
||||
|
||||
ALL[". . . 所有线程可见 . . ."]
|
||||
T1["线程 1"]
|
||||
T2["线程 2"]
|
||||
T3["线程 3"]
|
||||
|
||||
SH --> ALL
|
||||
PT --> T1
|
||||
PT --> T2
|
||||
PT --> T3
|
||||
|
||||
style SH fill:#e8f5e9,stroke:#4caf50,color:#1b5e20
|
||||
style PT fill:#fff3e0,stroke:#ff9800,color:#e65100
|
||||
style SEP stroke-dasharray: 5 5,fill:#fafafa,stroke:#bbb
|
||||
style ALL stroke:none,fill:none,color:#999
|
||||
style T1,T2,T3 stroke:none,fill:none,color:#999
|
||||
```
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
| 选项 | 正误 | 原因 |
|
||||
|------|------|------|
|
||||
| A | ❌ 不选 | **堆是共享的**。所有线程通过相同的堆指针访问堆内存,所以动态分配的内存对所有线程可见。这也是为什么多线程并发访问同一堆对象需要加锁 |
|
||||
| B | ❌ 不选 | **文件描述符表是共享的**。`open()` 返回的文件描述符对所有线程有效,一个线程打开的文件可以在另一个线程中关闭 |
|
||||
| C | ✅ **正确** | **每个线程有自己独立的栈空间!** 每个线程调用函数时的局部变量、返回地址、函数参数都在自己的栈上。**互不干扰** |
|
||||
| D | ❌ 不选 | **信号处理函数是共享的**。注册了一个信号处理器后,该进程的所有线程都会响应这个处理逻辑 |
|
||||
|
||||
#### 为什么每线程独立栈?
|
||||
|
||||
这是设计上的必然选择:
|
||||
|
||||
1. **函数调用需要局部变量隔离**: `foo()` 在线程 1 中的 `x = 42` 不应该影响线程 2 中的 `x`
|
||||
2. **返回地址独立**: 每个线程调用同一个函数,返回地址各自不同
|
||||
3. **防止栈溢出牵连其他线程**: 如果一个线程的栈爆了,不会影响其他线程的执行
|
||||
|
||||
#### 对比:进程 vs 线程
|
||||
|
||||
| 资源 | 进程间 | 线程间 |
|
||||
|------|--------|--------|
|
||||
| 地址空间 | ❌ 独立 | ✅ 共享 |
|
||||
| 堆 | ❌ 独立 | ✅ 共享 |
|
||||
| 栈 | ❌ 独立 | ❌ 独立(每线程一块) |
|
||||
| 文件描述符 | ❌ 独立(可inherit via fork) | ✅ 共享 |
|
||||
| 通信成本 | 高 (IPC) | 低(直接读写共享内存) |
|
||||
|
||||
> [!tip] Go 语言的 goroutine 区别
|
||||
> Goroutine 和线程类似——每个 goroutine 有自己的栈。但 Go 使用**可增长的栈**(初始仅 2KB),且调度器从用户态出发,比系统级线程更轻量。
|
||||
|
||||
</details>
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, TCP, 网络, 协议]
|
||||
create time: 2026-05-16 15:05
|
||||
---
|
||||
|
||||
# 07 - 单选题 5:TCP 滑动窗口
|
||||
|
||||
## 题目
|
||||
|
||||
关于 TCP 的**滑动窗口机制**,以下说法**正确**的是:
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | 滑动窗口大小由接收方根据其可用缓冲区大小动态通告给发送方 |
|
||||
| B | 滑动窗口只解决拥塞控制问题,与流量控制无关 |
|
||||
| C | 滑动窗口一旦确定就不能再改变 |
|
||||
| D | 发送窗口的上限等于接收窗口 + 拥塞窗口 |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**A**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### TCP 滑动窗口是什么?
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "发送窗口"
|
||||
S1["已发送已确认"]
|
||||
S2["可发送(空闲)"]
|
||||
S3["不能发送(超出窗口)"]
|
||||
end
|
||||
|
||||
subgraph "时间 →"
|
||||
R1["recv"] --> R2["send ACK"] --> R3["窗口右移"]
|
||||
end
|
||||
|
||||
S1 -.-> 已经成功传输
|
||||
S2 -.-> 可以填入新数据
|
||||
S3 -.-> 必须等待ACK
|
||||
```
|
||||
|
||||
**核心概念**: 发送端维护一个"窗口",窗口内的字节允许发送。当收到 ACK,窗口向前滑动,腾出空间继续发送。
|
||||
|
||||
#### 两种窗口机制
|
||||
|
||||
| 机制 | 控制方 | 目的 |
|
||||
|------|--------|------|
|
||||
| **接收窗口 (rwnd)** | 接收方通告 | **流量控制** — 防止发送太快撑爆接收缓冲 |
|
||||
| **拥塞窗口 (cwnd)** | 发送端计算 | **拥塞控制** — 防止网络被塞满 |
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "发送端的实际窗口"
|
||||
W["有效窗口 = min rwnd, cwnd"]
|
||||
end
|
||||
|
||||
R["接收窗口 rwnd"] -->|"接收方在ACK中通告"| E[有效窗口]
|
||||
C["拥塞窗口 cwnd"] -->|"发送端算法推算"| E
|
||||
|
||||
E -.->|限制| SF["发送速率"]
|
||||
|
||||
style E fill:#f9d09c
|
||||
```
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
| 选项 | 正误 | 原因 |
|
||||
|------|------|------|
|
||||
| A | ✅ **正确** | **接收方通过 TCP 首部中的 "Window Size" 字段通知发送方自己还有多少缓冲区可用**。这属于流量控制范畴 |
|
||||
| B | ❌ | **混淆了概念**。滑动窗口同时解决两个问题: rwnd 解决**流量控制**(receiver rate),cwnd 解决**拥塞控制**(network capacity)。说"只解决拥塞"是错误的 |
|
||||
| C | ❌ | rwnd **是动态变化的**! 接收方的缓冲区使用量一直在变,所以在每个 ACK 中都重新通告新的 Window Size。这就是"滑动"的含义之一 |
|
||||
| D | ❌ | 反了! 有效窗口取的是 **min(rwnd, cwnd)**, 即两者中的较小值。不是加和关系。如果 cwnd < rwnd,说明网络瓶颈; 如果 rwnd < cwnd,说明接收方处理不过来 |
|
||||
|
||||
#### 滑动窗口完整时序
|
||||
|
||||
```
|
||||
发送端 接收端
|
||||
| |
|
||||
|──── data(1-10) ───────────────→│ rwnd = 10000 bytes
|
||||
| │
|
||||
|←─── ACK 10, win=8000 ─────────│ ACK: 期待下一个序号=10
|
||||
| │ Window = 8000 bytes
|
||||
|──── data(11-20) ──────────────→│
|
||||
|←─── ACK 20, win=6000 ─────────│ rwnd 减小(接收缓冲被占用)
|
||||
| │
|
||||
|←─── ACK 20, win=9000 ─────────│ rwnd 增大(应用层消费了数据)
|
||||
```
|
||||
|
||||
> [!note] 四个关键算法
|
||||
> - **慢开始 (Slow Start)**: cwnd 指数增长
|
||||
> - **拥塞避免 (Congestion Avoidance)**: cwnd 线性增长
|
||||
> - **快重传 (Fast Retransmit)**: 收到 3 个重复 ACK 立即重传
|
||||
> - **快恢复 (Fast Recovery)**: 不降到 1 MSS,而是减半后继续
|
||||
|
||||
> [!tip] 一句话总结
|
||||
> 滑动窗口 = 流量控制 (rwnd) + 拥塞控制 (cwnd),取两者的较小值作为实际发送窗口。
|
||||
|
||||
</details>
|
||||
@@ -1,135 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, Go, 函数, 传参]
|
||||
create time: 2026-05-16 15:10
|
||||
---
|
||||
|
||||
# 08 - 单选题 6:参数传递
|
||||
|
||||
## 题目
|
||||
|
||||
在 Go 语言中,考虑以下代码:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// 第 1 组
|
||||
a := []int{1, 2, 3}
|
||||
f1(a)
|
||||
fmt.Println(a) // ?
|
||||
|
||||
// 第 2 组
|
||||
b := map[string]int{"x": 10}
|
||||
f2(b)
|
||||
fmt.Println(b) // ?
|
||||
|
||||
// 第 3 组
|
||||
c := &struct{ v int }{v: 100}
|
||||
f3(c)
|
||||
fmt.Println(*c) // ?
|
||||
|
||||
// 第 4 组
|
||||
d := 10
|
||||
f4(d)
|
||||
fmt.Println(d) // ?
|
||||
}
|
||||
|
||||
func f1(s []int) { s[0] = 99 }
|
||||
func f2(m map[string]int) { m["x"] = 20 }
|
||||
func f3(p *struct{ v int }) { p.v = 300 }
|
||||
func f4(n int) { n = 400 }
|
||||
```
|
||||
|
||||
以上 `fmt.Println` 的输出结果分别是:
|
||||
|
||||
| 选项 | 第1组 (切片a) | 第2组 (映射b) | 第3组 (结构体指针c) | 第4组 (整数d) |
|
||||
|------|-------------|-------------|-------------------|-------------|
|
||||
| A | `[99 2 3]` | `map[x:20]` | `{300}` | `10` |
|
||||
| B | `[1 2 3]` | `map[x:10]` | `{100}` | `400` |
|
||||
| C | `[99 2 3]` | `map[x:10]` | `{300}` | `10` |
|
||||
| D | `[99 2 3]` | `map[x:20]` | `{100}` | `10` |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**A**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### Go 的参数传递原则 ⭐
|
||||
|
||||
> [!info] 核心规则
|
||||
> **Go 永远都是值传递(pass by value)**——每个参数的值是实参的一份拷贝。但如果拷贝的是"引用类型"(底层数据结构),那么对内容的修改会反映到原始数据上。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "值传递的本质"
|
||||
A["main: a → header{s,p,h}"] -->|"复制header"| B["f1: s → header{s,p,h}"]
|
||||
end
|
||||
|
||||
subgraph "但底层数组 h 是共享的"
|
||||
H["📦 堆上的数组"]
|
||||
A -.->|p指向| H
|
||||
B -.->|p指向同一地址| H
|
||||
end
|
||||
|
||||
B -->|"s[0] = 99 → 写H[0]"| H
|
||||
H -->|"main看到变化"| A
|
||||
```
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
**第 1 组:切片 (`[]int`)**
|
||||
|
||||
```go
|
||||
func f1(s []int) { s[0] = 99 } // ❌ 等等,这个能改吗?
|
||||
```
|
||||
|
||||
- 切片的底层是一个**结构体**(slice header): `struct{ Data *int; Len int; Cap int }`
|
||||
- 传入函数时,**这个结构体被值拷贝了**
|
||||
- 但 `Data` 指针指向的是**同一个底层数组**!
|
||||
- 所以 `s[0] = 99` 通过共享的底层数组修改了原数据 → **输出 `[99 2 3]`** ✓
|
||||
|
||||
> [!tip] 但是... 如果是在函数里 `s = append(s, 4)` 呢?
|
||||
> 那就要分情况看: 如果 cap 足够大(追加不触发扩容),`Data` 共享 → 影响原切片; 如果触发扩容 → 分配新数组 → **不影响**原切片。
|
||||
|
||||
**第 2 组:映射 (`map[string]int`)**
|
||||
|
||||
- map 也是引用类型,它的值是 `*hmap`(一个内部结构的指针)
|
||||
- 传入函数后拷贝的是指针本身,但指向同一个 `hmap`
|
||||
- `m["x"] = 20` 修改了共享的 map → **输出 `map[x:20]`** ✓
|
||||
|
||||
**第 3 组:结构体指针 (`*struct{ v int }`)**
|
||||
|
||||
- 值传递拷贝的是指针的值(即地址)
|
||||
- `p.v = 300` 通过这个地址修改了原始结构体 → **输出 `{300}`** ✓
|
||||
|
||||
**第 4 组:基本类型 (`int`)**
|
||||
|
||||
- int 是值类型,传入时拷贝了数值 `10`
|
||||
- `n = 400` 只改了局部副本,不影响原始变量 → **输出 `10`** ✓
|
||||
|
||||
#### 汇总表
|
||||
|
||||
| 类型 | 本质 | 拷贝内容 | 函数内修改内容 | 是否影响外部 |
|
||||
|------|------|---------|--------------|-----------|
|
||||
| `[]int` 切片 | 引用类型 | slice header(含指针) | `s[i] = x` | ✅ 是 |
|
||||
| `map[K]V` | 引用类型 | 指针 `*hmap` | `m[k] = v` | ✅ 是 |
|
||||
| `*T` 指针 | 引用类型 | 地址值 | `p.field = v` | ✅ 是 |
|
||||
| `int` 等基础类型 | 值类型 | 实际数值 | `n = v` | ❌ 否 |
|
||||
| `struct{T}` 结构体 | 值类型 | 所有字段 | `s.f = v` | ❌ 否 |
|
||||
|
||||
#### 易错陷阱题
|
||||
|
||||
```go
|
||||
// 陷阱: 看似修改了切片,实际没有
|
||||
func badAppend(s []int) {
|
||||
s = append(s, 4) // 如果触发了扩容, newSlice != originalSlice
|
||||
}
|
||||
// 正确做法: return append(s, 4) 或者传 *[]int
|
||||
```
|
||||
|
||||
> [!warning] 面试高频
|
||||
> "Go 到底是传值还是传引用?" → **答案是传值**。只不过有些值的类型恰好是指针/引用类型。这和 Java 的说法一致:"Java 只有值传递"。
|
||||
|
||||
</details>
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, 图, BFS, DFS, 数据结构]
|
||||
create time: 2026-05-16 15:15
|
||||
---
|
||||
|
||||
# 09 - 单选题 7:图的遍历(BFS vs DFS)
|
||||
|
||||
## 题目
|
||||
|
||||
关于无向图的**广度优先搜索 (BFS)** 和 **深度优先搜索 (DFS)**,以下说法**正确**的是:
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | BFS 使用栈作为辅助数据结构,DFS 使用队列作为辅助数据结构 |
|
||||
| B | BFS 找到的两点间路径一定是最短路径(每条边的权重相等时) |
|
||||
| C | DFS 的空间复杂度始终优于 BFS |
|
||||
| D | BFS 不能用于检测图中的环,只有 DFS 可以 |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**B**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### BFS vs DFS 核心对比
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "BFS"
|
||||
Q["📋 队列 Queue"] -->|"FIFO, 先进先出"| L1["按层访问"]
|
||||
end
|
||||
|
||||
subgraph "DFS"
|
||||
S["📚 栈 Stack / Recursion"] -->|"LIFO, 后进先出"| L2["深入到底再回溯"]
|
||||
end
|
||||
```
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
| 选项 | 正误 | 原因 |
|
||||
|------|------|------|
|
||||
| A | ❌ | **反了!** BFS 用**队列**(Queue),DFS 用**栈**(Stack)。BFS 一层一层地往外扩展,队列天然适合;DFS 深入挖掘到底后回溯,栈天然适合 |
|
||||
| B | ✅ **正确** | 在**无权图**(或等权图)中,BFS 按层遍历,第一次到达目标节点时的路径就是最短路径。这是 BFS 最重要的性质之一 |
|
||||
| C | ❌ | 不一定。最坏情况下 BFS 需要存储整层节点,空间 O(|V|); DFS 需要存储从根到叶子的路径 + 未探索的兄弟节点,空间 O(|E|)。对于稠密图,DFS 可能比 BFS 更耗内存 |
|
||||
| D | ❌ | **BFS 也能检测环!** BFS 中如果发现遇到一个已访问的节点且不是当前节点的前驱,就说明存在环。虽然 DFS 判环更直观(利用递归栈的 colored 标记法),但说"只有 DFS 可以"是错的 |
|
||||
|
||||
#### BFS 为什么能找到最短路径?
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "BFS 层层扩张"
|
||||
R["起点 S"] --> L1["第1层: A, B"]
|
||||
L1 --> L2["第2层: C, D, E"]
|
||||
L2 --> L3["第3层: F"]
|
||||
end
|
||||
|
||||
style R fill:#a8e6cf
|
||||
style L1 fill:#ffd3b6
|
||||
style L2 fill:#ffaaa5
|
||||
style L3 fill:#ff8b94
|
||||
```
|
||||
|
||||
**核心证明思路**: BFS 按距离 d = 0, 1, 2, ... 的顺序访问节点。假设存在一条更短的路径长度为 k < d,那么 BFS 会在第 k 层就发现目标节点,矛盾。所以首次到达时的路径必定最短。
|
||||
|
||||
#### BFS vs DFS 完整对比表
|
||||
|
||||
| 特性 | BFS | DFS |
|
||||
|------|-----|-----|
|
||||
| 数据结构 | 队列 (Queue) | 栈 (Stack) / 递归 |
|
||||
| 访问顺序 | 逐层扩展 | 沿路深入到底 |
|
||||
| 最短路径 | ✅ 无权图中保证 | ❌ 不保证 |
|
||||
| 空间复杂度 | O(|V|)(最坏存一整层) | O(|V|)(最坏是深树) |
|
||||
| 能否检测环 | ✅(遇到已访问的非父节点) | ✅(遇到已访问且在递归栈中的节点) |
|
||||
| 适用场景 | 最短路径、层次遍历 | 拓扑排序、连通分量、路径查找 |
|
||||
| 是否能找到解 | ✅ 如果解靠近起点 | ✅ 如果解靠近某条深路径 |
|
||||
|
||||
> [!tip] 记忆技巧
|
||||
> - **BFS = Breadth First Search** → 横向扫 → **队列** → 短路
|
||||
> - **DFS = Depth First Search** → 纵向钻 → **栈** → 深入
|
||||
|
||||
</details>
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, Hash, 数据结构, 散列表]
|
||||
create time: 2026-05-16 15:20
|
||||
---
|
||||
|
||||
# 10 - 单选题 8:哈希桶(Hash Bucket)
|
||||
|
||||
## 题目
|
||||
|
||||
关于**拉链法(Chaining)**实现的哈希表,以下说法**错误**的是:
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | 每个哈希桶是一个链表,所有哈希值冲突的键值对存储在同一个链表中 |
|
||||
| B | 装载因子 α = n/m(n 为元素个数,m 为桶数),α 越大平均查找时间越长 |
|
||||
| C | 当使用红黑树替代链表来解决冲突时,最坏情况下的查找时间复杂度从 O(n) 降为 O(log n) |
|
||||
| D | 理想情况下,哈希表的每次插入、查找、删除操作的时间复杂度均为 O(1),因此不需要考虑扩容问题 |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**D**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### 拉链法哈希表结构
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "哈希表 buckets"
|
||||
B0["bucket[0]"] --> N0["(k1, v1) → (k4, v4) → nil"]
|
||||
B1["bucket[1]"] --> N1["nil"]
|
||||
B2["bucket[2]"] --> N2["(k2, v2) → nil"]
|
||||
B3["bucket[3]"] --> N3["(k5, v5) → (k8, v8) → (k11, v11) → nil"]
|
||||
B4["bucket[4]"] --> N4["(k3, v3) → nil"]
|
||||
end
|
||||
|
||||
H["hash(key) % m"] -->|"key=k4 得 3"| B3
|
||||
H -->|"key=k7 得 1"| B1
|
||||
```
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
| 选项 | 正误 | 原因 |
|
||||
|------|------|------|
|
||||
| A | ✅ 正确 | **这就是拉链法的定义**。每个桶是一个链表(或其他容器),冲突的元素挂到同一链表中 |
|
||||
| B | ✅ 正确 | α 越大 → 链表越长 → 遍历时间越多。在均匀哈希假设下,平均查找时间就是 O(1+α)。所以 α 确实影响性能 |
|
||||
| C | ✅ 正确 | Java HashMap 和 Go map 都采用了这个优化:当单个链表长度超过阈值时,链表→红黑树转换,防止哈希碰撞攻击导致的最坏退化 |
|
||||
| D | ❌ **错误(本题答案)** | **扩容是必须的!** 随着元素增多,α 增大,链表变长,性能退化为 O(n)。扩容(rehash)将所有元素重新分配到更多桶中,恢复 O(1) 性能 |
|
||||
|
||||
#### 扩容机制
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant H as HashMap
|
||||
participant T as Threshold
|
||||
participant R as Rehash
|
||||
|
||||
H->>T: put(k, v)
|
||||
T->>H: α = n/m > 阈值?
|
||||
alt 是
|
||||
H->>R: 分配 2x 大小的新数组
|
||||
R->>H: 所有元素 rehash到新位置
|
||||
H->>H: m = 2m
|
||||
else 否
|
||||
H->>H: 直接插入
|
||||
end
|
||||
```
|
||||
|
||||
#### 常见负载因子阈值
|
||||
|
||||
| 语言/实现 | 默认负载因子 | 触发扩容条件 |
|
||||
|-----------|------------|-------------|
|
||||
| Java HashMap | 0.75 | α ≥ 0.75 |
|
||||
| Go map | 6.5 | avg ≈ n/m ≥ 6.5(loadLoad 条件) |
|
||||
| Python dict | ~0.66 | 空位少于 1/3 时扩容 |
|
||||
|
||||
> [!warning] ⚠️ Go map 的特殊性
|
||||
> Go 的 map 不像 Java HashMap 那样设了固定负载因子就扩容。Go 的扩容条件是渐进式的:当 `avg = count/bucket ≥ 6.5` 且满足 overLoadFactor 条件时才逐步迁移(渐增式 rehash)。
|
||||
|
||||
#### 查找复杂度分析
|
||||
|
||||
\[
|
||||
&\text{均匀哈希假设下:} \\
|
||||
&\text{平均查找: } O(1 + \alpha) \\
|
||||
&\text{最坏情况(全部冲突): } O(n) \\
|
||||
&\text{带树优化的最坏情况: } O(\log n)
|
||||
\]
|
||||
|
||||
> [!tip] 一句话总结
|
||||
> 哈希桶虽然理论上是 O(1),但现实中必须关注扩容策略,否则在极端情况下会退化到近乎链表的性能。
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, HTTP, 网络, Web]
|
||||
create time: 2026-05-16 15:25
|
||||
---
|
||||
|
||||
# 11 - 单选题 9:HTTP 响应头
|
||||
|
||||
## 题目
|
||||
|
||||
一个 HTTP 响应消息中,以下哪个响应头字段用于**告诉客户端(浏览器)在多久之内不需要再次请求该资源**?
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | `Content-Length` |
|
||||
| B | `Cache-Control: max-age=3600` |
|
||||
| C | `Transfer-Encoding` |
|
||||
| D | `Content-Type` |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**B**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### 各字段的含义
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["HTTP Response"] --> Header["响应头 Headers"]
|
||||
Header --> BL["Content-Length: 表示主体字节数"]
|
||||
Header --> CC["Cache-Control: 控制缓存行为"]
|
||||
Header --> TE["Transfer-Encoding: 传输编码方式"]
|
||||
Header --> CT["Content-Type: 媒体类型"]
|
||||
```
|
||||
|
||||
#### 逐项分析
|
||||
|
||||
| 选项 | 正误 | 原因 |
|
||||
|------|------|------|
|
||||
| A | ❌ | `Content-Length` 只说明响应体的**字节大小**,和缓存无关 |
|
||||
| B | ✅ **正确** | `Cache-Control: max-age=3600` 告诉浏览器:这个响应的响应体在**未来 3600 秒内**是新鲜的,可以直接使用本地缓存副本,无需向服务器发请求 |
|
||||
| C | ❌ | `Transfer-Encoding` 指定了传输层的编码方式(如 `chunked`),用于不定长数据流式传输,与缓存无关 |
|
||||
| D | ❌ | `Content-Type` 声明资源的媒体类型(如 `text/html`, `application/json`),让浏览器知道如何解析内容,不涉及缓存时效 |
|
||||
|
||||
#### Cache-Control 常用指令
|
||||
|
||||
| 指令 | 含义 |
|
||||
|------|------|
|
||||
| `max-age=N` | 资源在 N 秒内被认为是新鲜的 |
|
||||
| `no-cache` | 不使用本地缓存(但可用),每次必须向服务器验证(条件请求) |
|
||||
| `no-store` | 完全不允许缓存任何形式的内容 |
|
||||
| `public` | 允许任意中间节点(CDN、代理、浏览器)缓存 |
|
||||
| `private` | 只允许浏览器缓存,不能由 CDN/代理共享 |
|
||||
| `s-maxage=N` | 只对共享缓存(如 CDN)生效,覆盖 `max-age` |
|
||||
|
||||
#### HTTP 缓存流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant B as "浏览器 (Browser)"
|
||||
participant S as "服务器 (Server)"
|
||||
|
||||
Note over B,S: 第一次请求
|
||||
B->>S: GET /index.html
|
||||
S-->>B: 200 OK + Cache-Control: max-age=3600
|
||||
|
||||
Note over B: 在接下来的 1小时内...
|
||||
B->>B: 直接使用本地缓存! 不发请求 ✨
|
||||
|
||||
Note over B,S: 3600秒后
|
||||
B->>S: GET /index.html (带 If-Modified-Since)
|
||||
S-->>B: 304 Not Modified (无身体) ← 只返回头部确认
|
||||
Note over B: 继续使用缓存并刷新计时器 ⏱️
|
||||
```
|
||||
|
||||
#### HTTP 缓存的完整层级
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "强缓存 (不回服务器)"
|
||||
BR["🌐 浏览器缓存"]
|
||||
end
|
||||
|
||||
subgraph "协商缓存 (回服务器验证)"
|
||||
CD["📡 CDN / Proxy"]
|
||||
VS["🖥️ 验证服务器"]
|
||||
end
|
||||
|
||||
BR -.->|命中 → 直接返回| DONE["✅ 200 (from disk/memory cache)"]
|
||||
BR -.->|未命中 / 过期 →| CD
|
||||
CD -.->|命中 → 直接返回| DONE
|
||||
CD -.->|未命中 →| VS
|
||||
VS -.->|304 Not Modified →| CD
|
||||
CD -.->|转发给浏览器| DONE
|
||||
VS -.->|200 OK + body →| CD
|
||||
```
|
||||
|
||||
> [!note] 区分两个容易混淆的状态码
|
||||
> - **200 (from memory cache)**: 浏览器从内存取,极快
|
||||
> - **200 (from disk cache)**: 浏览器从硬盘取,稍慢
|
||||
> - **304 Not Modified**: 协商缓存命中,服务器说"没变",客户端继续用旧副本
|
||||
|
||||
> [!tip] 面试常考组合
|
||||
> `ETag` + `If-None-Match`: 实体标签机制。服务器通过计算内容的哈希值生成 ETag,客户端下次请求时带上 `If-None-Match: <etag>`。如果内容未变,服务器返回 304。这比 `Last-Modified` + `If-Modified-Since` 更精确(因为时间精度只有秒级)。
|
||||
|
||||
</details>
|
||||
@@ -1,174 +0,0 @@
|
||||
---
|
||||
tags: [笔试, 微派, 树, 二叉搜索树, DP, Go]
|
||||
create time: 2026-05-16 15:30
|
||||
---
|
||||
|
||||
# 12 - 单选题 10:判断是否为 BST 的后序遍历
|
||||
|
||||
## 题目
|
||||
|
||||
给定一个整数数组,判断它是否是一个**二叉搜索树(BST)**的**后序遍历**结果。例如 `[1, 3, 2, 5, 7, 6, 4]` 是否是某个 BST 的后序遍历?
|
||||
|
||||
| 选项 | 内容 |
|
||||
|------|------|
|
||||
| A | 是,因为数组中数字不重复且可以构成 BST |
|
||||
| B | 否,因为后序遍历不能唯一确定一棵 BST |
|
||||
| C | 是,符合 BST 后序遍历的规则:右子树根之前的所有节点分为两组——小于根的在左子树侧,大于根的在右子树侧 |
|
||||
| D | 无法判断,需要同时给出前序和中序才能判断 |
|
||||
|
||||
<details>
|
||||
<summary>点击查看答案与解析</summary>
|
||||
|
||||
### ✅ 正确答案:**C**
|
||||
|
||||
---
|
||||
|
||||
### 详细解析
|
||||
|
||||
#### BST 后序遍历的特性
|
||||
|
||||
```
|
||||
后序遍历顺序: [左子树] → [右子树] → [根]
|
||||
└───── a ─────┘└───── b ─────┘└─ root ─┘
|
||||
|
||||
BST 性质: 左子树所有节点 < root < 右子树所有节点
|
||||
|
||||
所以合法的 BST 后序遍历必须满足:
|
||||
存在分界点 k,使得:
|
||||
· a[0...k-1] < root (全部小于根 → 左子树)
|
||||
· a[k...n-2] > root (全部大于根 → 右子树)
|
||||
```
|
||||
|
||||
#### 对 `[1, 3, 2, 5, 7, 6, 4]` 的分析
|
||||
|
||||
```
|
||||
根 = 4(最后一个元素)
|
||||
|
||||
扫描前面: 1, 3, 2, 5, 7, 6
|
||||
↑ ↑ ↑ ↑ ↑ ↑
|
||||
左 左 左 右 右 右
|
||||
|
||||
左子树部分 [1, 3, 2] → 全部 < 4 ✓
|
||||
右子树部分 [5, 7, 6] → 全部 > 4 ✓
|
||||
|
||||
继续递归:
|
||||
左子树: [1, 3, 2], 根=2
|
||||
[1] < 2 ✓, [] > 2 ✓ → 合法
|
||||
|
||||
右子树: [5, 7, 6], 根=6
|
||||
[5] < 6 ✓, [7] > 6 ✓ → 合法
|
||||
|
||||
结论: **是**合法的 BST 后序遍历 ✓
|
||||
```
|
||||
|
||||
#### 对应的 BST 结构
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
R["4"] --> L["2"]
|
||||
R --> RR["6"]
|
||||
L --> LL["1"]
|
||||
L --> LR["3"]
|
||||
RR --> RL["5"]
|
||||
RR --> RRR["7"]
|
||||
|
||||
style R fill:#a8e6cf
|
||||
```
|
||||
|
||||
```
|
||||
4
|
||||
/ \
|
||||
2 6
|
||||
/ \ / \
|
||||
1 3 5 7
|
||||
|
||||
后序: 1, 3, 2, 5, 7, 6, 4 ✓
|
||||
```
|
||||
|
||||
#### 反例分析
|
||||
|
||||
如果输入是 `[1, 5, 3, 4, 2]`:
|
||||
|
||||
```
|
||||
根 = 2
|
||||
前面: [1, 5, 3, 4]
|
||||
1 < 2 ← 应该是左子树
|
||||
5 > 2 ← 但这是右子树的数
|
||||
3 > 2 ← 右子树
|
||||
4 > 2 ← 右子树
|
||||
|
||||
问题: 左子树区出现了值 1 之后紧接着出现了 5, 3, 4
|
||||
而 5 > 2(不在左子树区间内),3 > 2,4 > 2
|
||||
但这看起来合法? 等等...
|
||||
|
||||
再仔细看: 1 < 2 ✓, 但后面的 5, 3, 4 都 > 2 ✓
|
||||
→ 这个也是合法的!
|
||||
|
||||
那换一个反例: [2, 1, 4, 3, 5]
|
||||
根 = 5
|
||||
[2, 1, 4, 3]: 全部 < 5 ✓ → 左子树
|
||||
没有右子树部分 ✓
|
||||
|
||||
递归 [2, 1, 4, 3], 根 = 3
|
||||
[2, 1]: 全部 < 3 ✓ → 左子树
|
||||
[4]: > 3 ✓ → 右子树 ✓
|
||||
|
||||
看起来也合法...
|
||||
|
||||
真正的反例: [3, 1, 4, 2, 5]
|
||||
根 = 5
|
||||
[3, 1, 4, 2]: 全部 < 5 ✓
|
||||
|
||||
递归 [3, 1, 4, 2], 根 = 2
|
||||
[3]: > 2 ✗ 应该全 ≤ 2,但 3 > 2
|
||||
然而 1 < 2
|
||||
|
||||
出现矛盾: 左侧区域混入了 > 2 的值 3
|
||||
→ 不合法! ❌
|
||||
```
|
||||
|
||||
#### O(n) 单调栈解法(进阶)⭐
|
||||
|
||||
虽然递归 O(n²) 也能解题,面试中常要求 O(n) 的单调栈解法:
|
||||
|
||||
```go
|
||||
func verifyPostorder(postorder []int) bool {
|
||||
stack := make([]int, 0, len(postorder))
|
||||
root := math.MaxInt64 // 代表"当前应该在右子树区域"的下界
|
||||
|
||||
for i := len(postorder) - 1; i >= 0; i-- {
|
||||
// 如果当前值 < root,说明它出现在某个祖先节点的左子树中
|
||||
// 但后续元素都必须 <= 那个祖先节点的左孩子
|
||||
if postorder[i] > root {
|
||||
return false
|
||||
}
|
||||
|
||||
// 从栈顶弹出比当前值大的元素
|
||||
for len(stack) > 0 && stack[len(stack)-1] > postorder[i] {
|
||||
root = stack[len(stack)-1] // 记录弹出的最大值作为下界
|
||||
stack = stack[:len(stack)-1]
|
||||
}
|
||||
|
||||
stack = append(stack, postorder[i])
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
> [!tip] 核心逻辑
|
||||
> 从后往前遍历,相当于**反向后序**(根 → 右 → 左)。用一个栈维护路径,当遇到下降趋势时弹出,弹出的最后一个值就是当前节点的**祖先下界**,后面不能再出现比这个下界大的值。
|
||||
|
||||
#### 复杂度对比
|
||||
|
||||
| 方法 | 时间 | 空间 |
|
||||
|------|------|------|
|
||||
| 递归分治 | O(n²) 最坏, O(n log n) 平均 | O(h) = O(n) 最坏 |
|
||||
| 单调栈 | O(n) | O(n) |
|
||||
|
||||
> [!question] 💡 思考题
|
||||
> 如果是**前序遍历**判定 BST,思路有什么变化?
|
||||
>
|
||||
> **答案**: 前序的第一个元素是根,然后找第一个大于根的元素为分界点,剩余部分应该全部大于根。验证通过后递归处理左右子树。也可以用类似的单调栈方法(正向遍历)。
|
||||
|
||||
</details>
|
||||
Reference in New Issue
Block a user